***
해당 글은 CORS에 대한 기본적인 배경과 개념도 다루고 있습니다.
시간이 없어서 바로 해결 방법을 확인하실 분은 3번으로 바로 가주세요!
1. CORS 배경 이해하기
웹개발을 하면서 누구나 마주하게 되는 악명높은 CORS 오류에 대한 모든 것을 이야기 해보겠다.
모든 오류는 구조를 알아야 올바르게 해결할 수 있다.
우선 CORS를 이해하기 위해서 선행으로 알아야 하는 개념이 있다.
* SOP (Same-origin policy)
SOP (Same-origin policy), 동일 출처 정책이라고 부른다.
이는 브라우저가 다른 출처에서 로드된 자원과 상호작용 하지 못하도록 요청 발생을 제하하고,
동일 출처 (Same Origin)에서만 접근이 가능한 정책이다.
동일 출처, 다른 출처? 대체 출처가 뭔데?
* 출처 (Origin)
출처란 Protocol, Host, Port까지 모두 합친 URL을 의미한다.
- Origin = Protocol + Host + Port
- 동일 출처란 Protocol, Host, Port 세가지 모두가 일치하는 것을 말한다.
⇒ 본인이 본인 서버에 있는 자원을 로드하는 것 - 다른 출처란 Protocol, Host, Port 중 단 하나라도 다른 출처를 의미한다.
⇒ React 프론트엔드에서 Django 백엔드 서버에 접근 (이 둘은 default 포트 번호가 다르다)
이해하기 쉽게 예를 들어보겠다.
Django 로컬 개발시기본 출처 : http://localhost:8000
React 로컬 개발시 기본 출처 : http://localhost:3000
http://localhost 라는 같은 protocol과 host를 가지고 있음에도 불구하고 포트 번호가 다르기 때문에
이 두 서버 간의 상호작용은 다른 출처로 간주한다.
동일 출처 정책이 필요한 이유
그렇다면 브라우저는 왜 불편하게 이러한 정책을 채택 했을까?
그 이유는 출처가 다른 어플리케이션 간 자유롭게 자원을 공유할 수 있는것은 위험하기 때문이다.
만약 제약이 없다면 해커는 CSRF, XSS 등의 방법을 통해 위조 요청을 보낼 수 있고
그로인해 우리 서버의 자원을 가로챌 수 있다.
* CSRF (Cross-Site Request Forgery)
* XSS (Cross-Site Scripting)
이를 통해 브라우저는 보안을 위해 다른 출처를 가진 자원을 서로 자유롭게 공유할 수 없도록 제한 한다는 것을 이해했다.
그런데 현시대 흐름을 보자 웹 생태계는 대부분 Front-End, Back-End로 분리되어 있다.
이는 각각 다른 언어, 다른 프레임워크를 사용한다. 이에 따라 다른 포트번호를 이용하고 있는 것이 대부분이다.
또한 Rest API의 발전으로 인해 실무에서는 다른 서버의 API를 끌어다 쓰는 일이 비일비재하다.
렇다면 어떻게 하면 다른 출처의 리소스를 허가하고 공유할 수 있을까?
이 때 등장 하는 개념이 바로 CORS이다.
2. CORS의 개념
CORS (Cross-Origin Resource Sharing)
CORS (Cross-Origin Resource Sharing), 교차 출처 리소스 공유라고 부른다.
이는 말 그대로 다른 출처의 리소스 공유에 대한 보안정책이다.
참고로 CORS는 브라우저에서만 발생하는 보안정책이다.
우리는 CORS 정책을 준수 함으로써 다른 출처의 리소스를 공유할 수 있다.
다시 말해 우리가 겪는 CORS 오류는 정책을 준수하지 않았기 때문에 일어난 오류이다.
즉 CORS 오류는 정책을 준수 함으로써 해결 할 수 있다.
CORS 동작 원리
- 클라이언트에서 Request Header에 Origin을 담아 전달
⇒ 본인의 출처를 담아 전송하면 된다. (이러한 출처에서 자원을 요청했다는 의미) - 서버는 Response Header에 Access-Control-Allow-Origin을 담아 반환
⇒ 해당 출처에 대해서는 자원 공유를 허용한다는 의미 - 브라우저는 클라이언트 측의 Origin과 서버의 Access-Control-Allow-Origin를 비교한다.
- 각각 일치할 경우 자원 공유를 허용, 다를 경우에는 CORS 오류를 발생시키고 공유를 제한한다.
CORS 동작 원리를 살펴보면 CORS 오류의 해결 방법을 이해할 수 있다.
클라이언트 측에서는 본인의 Origin을 담아 전달.
서버에서는 클라이언트의 Origin을 Access-Control-Allow-Origin에 명시.
Django와 React로 실습을 통해 해결 방안을 구현해보겠다.
3. CORS 오류 해결 실습 ( Django, React )
우선 로컬 환경 django 프로젝트에서 문자열을 반환하는 간단한 api를 작성하고
로컬 환경 react에서 해당 django api를 호출해보는 식으로 진행하겠다.
# Django / apis.py
@api_view(['GET'])
@permission_classes([AllowAny])
def test_api(request):
response_body = {
"msg" : "hello world!"
}
return Response(response_body, status=status.HTTP_200_OK)
해당 요청은 same origin으로 호출하는 것이기 때문에 문제 없이 호출되는 것을 확인할 수 있다.
다음은 axios를 통해 react측 호출 코드를 작성한 후 api를 호출 해보겠다.
// React / App.js
import axios from 'axios'
const axiosInstance = axios.create({
baseURL: "http://localhost:8000",
});
function App() {
const handleClick = async () => {
try {
const response = await axiosInstance.get("/api/test");
if (response.status === 200) {
console.log(response.data);
}
} catch (error) {
console.log(error);
}
};
return (
//... 생략
<div onClick={handleClick}>
api 호출
</div>
);
}
export default App;
Access to XMLHttpRequest at 'http://localhost:8000/api/test' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
예상대로 Django와 React는 로컬 환경에서 실행했으나
localhost라는 host명은 같지만 port 번호가 다르므로 CORS 오류가 발생한다.
이제 해결 해보겠다. Djgnao에서만 추가 설정을 해주면 됩니다. 단 5분이면 해결 할 수 있다.
django-cors-headers
django에는 cors 문제를 쉽게 해결할 수 있는 django-cors-headers 라는 라이브러리가 있다. 이를 활용하자
https://pypi.org/project/django-cors-headers/
pip install django-cors-headers
# settings.py
INSTALLED_APPS = [
# ... 생략
'corsheaders'
]
MIDDLEWARE = [
# ... 생략
"corsheaders.middleware.CorsMiddleware",
'django.middleware.common.CommonMiddleware',
# ...
]
CORS_ALLOWED_ORIGINS = [
"http://localhost:3000",
]
CORS_ALLOWED_ORIGINS => 해당 배열에 명시된 출처와 믿고 리소스를 공유하겠다는 설정이다.
이후 다시 react로 돌아가서 api를 호출해보면,
문제 없이 정상적으로 호출되는 것을 확인할 수 있다.
4. CORS 심화
인증된 요청 (Credentialed Request)
인증된 요청이란 클라이언트 측에서 서버에게 요청을 보낼 때 자격 인증 정보를 포함에 요청을 보내는 것이다.
자격 인증 정보란 우리가 흔히 아는 Authorization 헤더에 설정된 Token 혹은 Cookie 등을 말한다.
* CORS 준수해서 인증 정보 보내기
1. 클라이언트 측에서 인증 정보를 보내도록 설정하기
⇒ 기본적으로 브라우저는 자동으로 인증 관련 데이터를 request에 담지 않도록 되어있다.
그렇기 때문에 credentials 옵션을 통해 이를 허용한다는 설정을 해줘야한다.
2. 서버에서 인증된 요청에 대한 헤더 설정하기
⇒ 서버도 마찬가지로 credential 요청에 대해 대응해줘야한다.
a. Response Header에 Access-Control-Allow-Credentials를 True로 설정
b. Response Header에 Access-Control-Allow-Origin의 값에 와일드 카드 (*) 사용 불가능
c. Response Header에 Access-Control-Allow-Methods의 값에 와일드 카드 (*) 사용 불가능
d. Response Header에 Access-Control-Allow-Headers의 값에 와일드 카드 (*) 사용 불가능
이 항목 들은 인증 정보는 민감한 정보기이 때문에 출처를 정확하게 설정해 줘야 함을 뜻한다.
// React, axios를 선언하는 부분에 withCredentials true로 설정
import axios from "axios";
axios.defaults.withCredentials = true;
const axiosInstance = axios.create({
baseURL: "http://localhost:8000",
});
# Django / settings.py
CORS_ALLOW_HEADERS = (
"accept",
"authorization",
"content-type",
"user-agent",
"credentials",
"host",
"origin",
"X-CSRFToken",
"x-csrftoken",
"csrftoken",
"x-requested-with",
)
CORS_ALLOW_CREDENTIALS = True
이 두 설정을 통해 인증된 요청을 할 수 있다.
'Framework > Django & DRF' 카테고리의 다른 글
[Django] html에 static 파일 적용시키기 (css, js 등) (1) | 2025.01.27 |
---|---|
[Django] 국세청 사업자등록정보 진위확인 및 상태조회 API 사용법 ( + Postman) (0) | 2025.01.15 |
[Django] DRF 테스트 코드 작성하기 (TDD, tests.py) (1) | 2025.01.09 |
[Django] 초심자에게 발생하는 에러 : didn't return an HttpResponse object (0) | 2023.02.26 |
[Django] table 생성 시 Value Error (1) | 2023.02.26 |