웹의 요청에는 다양한 종류가 존재합니다. CORS의 동작을 이해하기 위해서는 아래의 3종류의 요청의 차이를 알아두어야 이해할 수 있습니다.
simple request
는 하나의 본 요청을 전송하여 응답을 전달받는 요청을 의미합니다. 흔히 client-server
모델의 요청을 생각하실때 떠올리시는 요청입니다. HTTP Request
를 생각하시면 한번의 요청-응답으로 동작하시는 것을 많이 생각하지만 실제로는 요청에 따라 사전요청을 보내기도 합니다. 이러한 사전요청을 보내지 않기 위해서, 즉 simple request
를 보내기 위해서는 몇가지 조건을 충족시켜야 합니다.
GET
,POST
,HEAD
중 하나이어야합니다.Accept
,Accept Language
,Content-Language
,Content-Type
만 가능합니다. 또한 Content-Type
헤더의 값으로는 application/x-www-form-urlencoded
,multipart/form-data
,text/plain
중 하나로만 설정할 수 있습니다.위의 조건을 충족하지 못하는 요청은 preflight
를 통해 서버가 처리 가능한 요청인지 확인 후 요청을 보내게 됩니다.
preflight request
는 본요청을 처리하기 앞서, 서버측이 처리 가능한 요청인지 확인하기 위해 보내는 요청입니다. 위의 simple request
의 조건을 충족시키지 못하는 요청들은 preflight request
를 서버로 전송하게 됩니다. preflight request
는 사용할 HTTP method
, 클라이언트의 커스텀 헤더 존재 여부 등에 관한 메타데이터를 포함합니다. 서버는 이 메타데이터를 보고 해당 브라우저의 요청을 받아들일지 결정하여 알려주는 것입니다. CORS
요청의 경우 preflight request
에 몇가지 정보를 저장하여 보내게 되며, 서버에서는 이 헤더값을 확인하여 처리가능한 요청인지 검증하게 됩니다. 이 preflight
요청과정에서 CORS
원칙에 위배되어 본 요청 자체가 전송되지 않을 수 있으며, 자세한 내용은 아래의 CORS 동작
에서 자세히 알아보도록 하겠습니다.
preflight
에서는 OPTIONS
메소드를 사용하여 요청을 보냅니다. 이때 본요청에서 CORS 요청을 처리할 수 있을지 판단하는데 필요한 값을 전송하기 위해 Access-Control-Allow-*
키의 헤더값을 포함하여 전송하게 됩니다.
서버는 preflight
에 위와 같은 정보를 활용하여 본 요청이 서버의 CORS 정책에 부합하는지 판단하여 본 요청을 처리할 수 있는지 없는지의 여부를 응답으로 알려주게 됩니다.
요청에 인증 정보와 같은 세션, 쿠키의 정보가 포함된 요청을 의미합니다. 기본적으로 XMLHttpRequest
와 같은 요청은 브라우저의 세션, 쿠키 정보를 요청에 포함시키지 않습니다. 이때 요청에 인증과 관련된 정보를 담을 수 있게 해주는 옵션이 credential
옵션입니다. 설정 가능한 옵션으로는 same-origin
, include
, omit
이 있습니다.
CORS 설정을 위해서는 Django에서 설정해주어야 하는 여러 환경값이 있습니다. django-cors-headers
에서 제공하는 CorsMiddleware
코드의 동작을 바탕으로 CORS error 해결과정을 정리해보겠습니다.
CorsMiddleware
는 django에서 제공하는 MiddlewareMixin
을 상속받아 미들웨어를 구현하였습니다. 따라서 CorsMiddleware
는 MiddlewareMixin
에 구현된 기본적인 동작을 따라 다양한 기능들을 수행하게 됩니다. MiddlewareMixin
을 상속받고, setting.py
의 MIDDLEWARE
리스트에 추가된 미들웨어는 __call()__
메소드가 호출되며 아래와 같은 메소드들이 정의된 경우, 순차적으로 실행되며 동작하게 됩니다.
1. process_request(request)
2. get_response(request)
3. process_response(request, response)
요청의 CORS 관련 로직을 설정된 환경값에 따라 처리하게 되는 단계입니다. 각 중요 메소드의 동작들을 정리하면 아래와 같습니다.
class CorsMiddleware(MiddlewareMixin):
def process_request(self, request: HttpRequest) -> HttpResponse | None:
# CORS preflight인 경우, body가 비어있는 200 Response 반환
# 내부 동작
# 1. Cors 요청이 허용된 url인지 확인
# 2. OPTIONS 메소드 요청여부, request.META에 HTTP_ACCESS_CONTROL_REQUEST_METHOD 여부 확인
def process_response(
self, request: HttpRequest, response: HttpResponse
) -> HttpResponse:
# cors가 허용된 요청인경우,
# 요청 헤더의 Origin 헤더값 확인
# conf.CORS_ALLOW_CREDENTIALS가 True인 경우
# ->응답 헤더의 Access-Control-Allow-Credentials 값 true 추가
# conf.CORS_ALLOW_ALL_ORIGINS 가 True이고, conf.CORS_ALLOW_CREDENTIALS 설정 안되어있으면
# -> 응답 헤더의 Access-Control-Allow-Origin 값 *로 설정
# -> 그렇지 않은 경우 요청의 Origin 값으로 설정
# request method가 OPTIONS인 경우
# 응답 헤더의 Access-Control-Allow-Headers에 conf.CORS_ALLOW_HEADERS 값 추가
# 응답 헤더의 Access-Control-Allow-Methods에 conf.CORS_ALLOW_METHODS 값 추가
def regex_domain_match(self, origin: str) -> bool:
# conf.CORS_ALLOWED_ORIGIN_REGEXES에 정의된 허용된 origin의 정규표현식과
# 요청의 origin이 매칭되는지 여부 반환
def is_enabled(self, request: HttpRequest) -> bool:
# conf.CORS_URLS_REGEX와 request.path_info가 매칭여부 확인
def origin_found_in_white_lists(self, origin: str, url: SplitResult) -> bool:
# 요청의 Origin이 알맞은 CORS origin인지 확인
return (
(origin == "null" and origin in conf.CORS_ALLOWED_ORIGINS)
or self._url_in_whitelist(url)
or self.regex_domain_match(origin)
)
def _url_in_whitelist(self, url: SplitResult) -> bool:
# conf.CORS_ALLOWED_ORIGINGS에 요청의 Origin이 있는지 확인
위의 코드 동작의 흐름을 정리하면 아래와 같습니다.
process_request() -> HttpResponse
요청을 받아 기본 응답을 생성하는 메소드입니다. 생성된 HTTPResponse는 아래의 process_response()
로 전달됩니다.
process_response() -> HttpResponse
요청의 처리 결과에 따라 응답에 알맞은 데이터를 넣어 응답을 완성하는 메소드입니다. preflight
나 본요청의 요청 처리에 따라 응답의 header
/body
데이터를 업데이터하여 최종 응답으로 반환하게 됩니다.
CORS 요청에 관여하게 되는 django.conf
값은 크게 4가지 입니다.
CORS_ALLOW_CREDENTIALS
설정은 CORS 요청에서 credential request
도 허용할 것인지에 관한 설정입니다. True
로 설정할 경우, 응답의 Access-Control-Allow-Credentials
헤더의 값이 true
로 설정됩니다.
CORS_ALLOW_ALL_ORIGINS
설정은 CORS 요청을 허용할 Origin에 관한 설정입니다. 요청이 들어올 때마다 해동 요청의 Origin
헤더값이 CORS_ALLOW_ALL_ORIGINS
에 등록된 값인지 확인하여 요청을 처리 여부를 경정하게 됩니다.
ACCESS_CONTROL_ALLOW_HEADERS
설정은 CORS 요청에서 허용할 헤더의 키값들의 목록을 정의해둔 설정값입니다.
ACCESS_CONTROL_ALLOW_METHODS
설정은 CORS 요청에서 허용할 요청의 method 목록을 정의해둔 설정값입니다.
def process_response(
self, request: HttpRequest, response: HttpResponse
) -> HttpResponse:
# 중략
origin = request.META.get("HTTP_ORIGIN") # 요청의 헤더 중 Origin 헤더를 추출
if not origin:
return response # Origin 헤더가 없는 경우 바로 응답 반환 -> 올바르지 못한 요청
# 중략
if conf.CORS_ALLOW_CREDENTIALS: # credential request를 허용하는 경우, Access-Control-Allow-Credentials 헤더 값이 true로 설정
response[ACCESS_CONTROL_ALLOW_CREDENTIALS] = "true"
# 중략
if (
not conf.CORS_ALLOW_ALL_ORIGINS
and not self.origin_found_in_white_lists(origin, url)
and not self.check_signal(request)
):
# 장고에서 모든 CORS 요청을 허용하지 않고, 해당 요청을 보낸 Origin이 허용되지 않은 요청인 경우 반환 -> 올바르지 못한 요청
return response
# 중략
if conf.CORS_ALLOW_ALL_ORIGINS and not conf.CORS_ALLOW_CREDENTIALS:
# 장고 설정이 모든 CORS를 허용하고, credential 요청을 허용하지 않으면, 모든 origin으로부터 CORS를 허용
response[ACCESS_CONTROL_ALLOW_ORIGIN] = "*"
else:
# 그렇지 않으면 요청을 보낸 origin만 허용
response[ACCESS_CONTROL_ALLOW_ORIGIN] = origin
# 중략
if request.method == "OPTIONS":
# Preflight인 경우, django에서 설정한 CORS method, header 리스트를 응답 헤더에 저장합니다.
response[ACCESS_CONTROL_ALLOW_HEADERS] = ", ".join(conf.CORS_ALLOW_HEADERS)
response[ACCESS_CONTROL_ALLOW_METHODS] = ", ".join(conf.CORS_ALLOW_METHODS)
# 중략
return response
위의 설정들이 동작하는 CorsMiddleware.process_response()
코드의 주요 부분만 요약한 내용입니다.
CORS_ALLOW_ALL_ORIGINS
-> bool
모든 Origin으로부터의 요청을 허용할 것인지에 대한 설정입니다. 허용할 것이면 True
, 그렇지 않으면 False
로 설정하면 됩니다.
CORS_ALLOW_CREDENTIALS
-> bool
credential request를 CORS 요청으로 허용할 것인지에 대한 설정입니다. True
로 설정되는 경우, Access-Control-Allow-Origin
헤더값을 와일드 카드로 설정할 수 없게 됩니다.
ACCESS_CONTROL_ALLOW_HEADERS
-> iterator
CORS 요청에서 허용할 헤더의 키값 목록들을 설정합니다. preflight
인 경우, 해당 목록들의 값들이 응답의 Access-Control-Allow-Headers
에 추가되어 반환됩니다. 따라서 시스템이 허용할 헤더값들을 열거해줍니다.
ACCESS_CONTROL_ALLOW_METHODS
-> iterator
CORS 요청으로 허용할 요청 메소드들을 설정합니다. 설정된 값들이 preflight
에 추가되며, 본 요청의 메소드가 허용된 메소드인 경우에 본요청이 서버로 전송되게 됩니다.
Access-Control-Allow-Methods
헤더에 추가되어 반환됩니다. 따라서 시스템이 제공/사용하는 API의 request method들을 모두 나열해주면 됩니다.