[Django] 장고 CORS 크로스 도메인 이슈

2020. 1. 16. 16:44프로그래밍/Django

Django로 API 서버를 만드는데 cors에러가 떴다 ... Cross Domain 이슈가 발생한건데, 이 문제는 왜 발생하는 걸까 ?

CORS

CORS는 Cross Origin Resource Sharing의 약자로

도메인 또는 포트가 다른 서버의 자원을 요청하는 매커니즘이다.

 

최근 대부분의 웹 브라우저는 Javascript를 이용한 AJAX(Asynchronous Javascript XML)통신을 통한 데이터 송수신을 하는데,

다른 도메인을 가진 서버의 URL을 호출해 데이터를 가져오려고 하는 경우

보안 문제인 Cross Domain 이슈를 발생시킨다.


왜냐하면 만약 우리 웹 서비스에서 사용하기 위해 다른 서브 도메인을 가진 API 서버를 구축했는데, 우리가 아닌 다른 웹 서비스에서 이 API 버서에 접근해서 마음대로 API를 호출해서 사용하면 안되기 때문이다.

 

예를들어, www.ozit.co.kr 도메인에선 www.ozit.co.kr 도메인 내에 있는 URL 만을 호출할 수 있다.
www.ozit.co.kr 도메인에서 www.tistory.com도메인의 URL은 AJAX로 호출할 수가 없다.

 

 

Javascript는 동일 출처 정책이라는 정책을 두어 도메인이 다른 서버로부터 요청을 받으면 보안 문제로 간주하고 CORS 이슈를 발생시켜 이를 차단한다.

 

동일 출처 정책(Same Origin Policy) 때문에 CORS가 발생하는데, 브라우저에서 외부 서버가 cross-origin HTTP로 요청한 데이터를 보안 목적으로 차단한다. 그래서 데이터를 받을 수가 없다.

즉, 자신과 동일한 도메인에 한해서만 요청을 허용하고 처리해준다.

 

CORS 문제 해결 방법

CORS 문제는 다른 도메인의 서버로부터 요청이 들어왔을 때, 헤더에 접근을 허락하는 내용이 없으면 발생한다.

예를 들어 클라이언트는 localhost:3000이고 외부 서버는 localhost:8000이면 포트가 달라서 CORS가 발생할 수 있다.

 

(가장 쉬운 해결방법은 클라이언트와 서버가 같은 도메인과 포트를 사용하는 거지만.. )


이럴 경우 외부 서버에서 보내는 요청의 헤더(Access-Control-Allow-Origin response 헤더)cross origin HTTP 요청을 허가해 접근을 허락하는 내용을 추가해 주면 된다.

 

cross origin HTTP 요청을 허가해주는 방법

헤더에 접근을 허락하는 내용을 추가

1. pip install로 django-cors-headers 앱을 설치한다.

django-cors-headers는 Cross-Origin Resource Sharing(CORS) 에 필요한 서버의 헤더를 조작하기 위한 Django 앱이다.

$ pip install django-cors-headers

django-cors-headers  장고앱을 이용해서 response에 CORS(Cross Origin Resource Sharing) 헤더를 추가한다,

이렇게 하면 다른 서버에서로부터 내 서버 앱으로 들어오는 브라우저를 통한 요청을 허가한다.

 

2. installed apps 목록에 corsheaders를 추가한다.

INSTALLED_APPS = [
    ...
    'corsheaders',
    ...
]

 

3. middleware class도 추가해야 한다.

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware', # <- 가능한 높게 위치시켜야 한다.
    'django.middleware.common.CommonMiddleware', 
    ...
]

CorsMiddleware는 가능한 높게 위치시켜야 한다.

특히 Django의 CommonMiddleware나 Whitenoise의 WhiteNoiseMiddleware와 같은 응답을 생성할 수 있는 미들웨어 전이어야 한다.

이러한 미들웨어 전이 아닌 경우엔, 이러한 응답들에 CORS 헤더를 추가할 수 없기 때문이다.

 

4. Django settings에서 미들웨어의 동작을 구성해야 한다.

CORS_ORIGIN_WHITELIST에는 cross-site 요청을 허용하는 호스트들을 추가한다.

또는 모든 호스트를 허용하고 싶으면 CORS_ORIGIN_ALLOW_ALL을 True로 설정한다. 만약 CORS_ORIGIN_ALLOW_ALL이 True 이면, whitelist는 쓰이지 않고, 모든 origin에서의 요청이 허용된다.

Default는 False이다.

 

CORS_ORIGIN_WHITELIST는 cross-site HTTP 요청을 할 수 있는 권한이 주어지는 origin들의 리스트이다.

Default는 [] 이다.

 

Origin은 URI scheme + hostname + port 등으로 정의된다.

CORS_ORIGIN_ALLOW_ALL = True # <- 모든 호스트 허용

# or 

CORS_ORIGIN_WHITELIST = (
    "https://example.com",
    "https://sub.example.com",
    "http://localhost:8080",
    "http://127.0.0.1:9000"
)

이렇게 헤더에 추가해주면 된다.

 

5. 5번부터는 쓰지 않아도 되는 선택적인 세팅이다. 몇 가지만 살펴보겠다.

 

CORS_ALLOW_CREDENTIALS

CORS_ALLOW_CREDENTIALS가 True인 경우, 쿠키가 cross-site HTTP 요청에 포함될 수 있다. 기본값은 False이다.

CORS_ALLOW_CREDENTIALS = True

 

CORS_ALLOW_METHODS

실제 요청에 허용되는 HTTP 동사 리스트이다. 기본값은 다음과 같다:

CORS_ALLOW_METHODS = [
    'DELETE',
    'GET',
    'OPTIONS',
    'PATCH',
    'POST',
    'PUT',
]

기본값은 corsheaders.defaults.default_methods로 임포트 할 수 있으므로, 커스텀 메소드들은 확장해서 사용하면 된다.

예를 들어:

from corsheaders.defaults import default_methods

CORS_ALLOW_METHODS = list(default_methods) + [
    'POKE',
]

 

CORS_ALLOW_HEADERS

실제 요청을 할 때 사용될 수 있는 non-standard HTTP 헤더 목록이다. 기본값을 다음과 같다:

CORS_ALLOW_HEADERS = [
    'accept',
    'accept-encoding',
    'authorization',
    'content-type',
    'dnt',
    'origin',
    'user-agent',
    'x-csrftoken',
    'x-requested-with',
]

이것 또한 기본값은 corsheaders.defaults.default_headers로 임포트 할 수 있으므로, custom 헤더들은 확장해서 사용하면 된다.

예를 들어:

from corsheaders.defaults import default_headers

CORS_ALLOW_HEADERS = list(default_headers) + [
	'my-custom-header'
]

 

SETTINGS.PY

위의 내용을 바탕으로 작성한 settings.py 파일의 일부이다.

INSTALLED_APPS = [
	'corsheaders',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'account',
    'product',
    'review',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'corsheaders.middleware.CorsMiddleware',
]

CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_CREDENTIALS = True

CORS_ALLOW_METHODS = (
    'DELETE',
    'GET',
    'OPTIONS',
    'PATCH',
    'POST',
    'PUT',
)

CORS_ALLOW_HEADERS = (
    'accept',
    'accept-encoding',
    'authorization',
    'content-type',
    'dnt',
    'origin',
    'user-agent',
    'x-csrftoken',
    'x-requested-with',
)

 


* URL과 도메인의 차이

1) URL
URL은 도메인을 포함하는 경로이다. 특정 웹사이트의 특정 위치까지 가기위한 전체 경로라고 보면 된다
출처: https://na27.tistory.com/149 [na27]
예를 들어 TEST 폴더 안의 TEST.PDF라는 파일은 http://도메인/test/test.pdf/ 가 URL이다.

 

2) 도메인
도메인은 예를 들어 http://www.naver.com 에서 www.naver.com이 도메인이다.

  • DNS(Domain Name System) 서버
    사용자가 웹사이트에 접속할 때 도메인 이름(www.naver.com)접속하면, DNS 서버에 도메인 이름이 보내진다. 그럼 DNS 서버는 그 도메인 이름에 연결된 아이피 주소(ex. 202.179.177.22 2017년 9월 17일 기준 네이버 웹사이트 IP 주소) 응답을 사용자 컴퓨터에 보낸다. 그럼 사용자는 응답받은 아이피 주소로 실제 데이터 처리를 하는 서버에 다시 데이터 요청을 하고 데이터를 응답 받게 된다.

Reference

https://pypi.org/project/django-cors-headers/

CORS 크로스 도메인 이슈 (No 'Access-Control-Allow-Origin' header is present on the requested resource)
URL가 도메인의 차이가 뭔가요?
Domain과 URL 이해
CORS 문제 해결 방법
CORS에 대한 간단한 고찰
Django, Js 교차 CORS 오류에 대한 대책

'프로그래밍 > Django' 카테고리의 다른 글

[Django] Custom Command (BaseCommand)  (0) 2020.05.08
[Django] 배포 - WSGI  (0) 2020.05.05