CORS는 최초에 리소스를 제공한 출처(origin)와 다른 출처의 리소스를 요청하는 경우(cross-origin 요청), 특정 HTTP header를 사용하여 웹 애플리케이션의 cross-origin 요청을 브라우저가 제한적으로 허용하는 정책이다.
URL의 구성요소 중 Protocol, Host, Port을 모두 합친 것을 출처라고 한다.
Protocol, Host, Port가 모두 동일하면 동일 출처로 인정된다.
<동일 출처 예시>
http://example.com:80
http://example.com ➡ 프로토콜, 호스트, 포트번호(생략 가능) 모두 같으므로 동일 출처
<다른 출처 예시>
http://example.com
https://example.com ➡ 프로토콜 다름
http://example.com
http://www.example.com ➡ 호스트(도메인) 다름
http://example.com
http://example.com:8080 ➡ 포트번호 다름
기본적으로 브라우저는 보안상의 이유로 Same-origin policy(SOP, 동일 출처 정책)을 따른다.
SOP: 동일 출처 정책
"같은 출처에서만 리소스를 공유할 수 있다"라는 규칙을 가진 정책
그렇기 때문에 XMLHttpRequest와 fetch API는 동일 출처 정책에 따라 동일한 출처의 리소스만을 요청할 수 있다.
하지만 웹 애플리케이션을 구현하다보면, 다른 출처에 있는 리소스를 요청하는 경우가 많다. 예를 들어 프론트 서버와 백엔드 서버가 따로 존재하는 경우 (프론트엔드 개발자가 React등을 사용할 때)
프론트 서버의 URL은 http://localhost:3000이고
백엔드 서버는 http://localhost:8080에 띄울 수 있다.
프론트 서버와 백엔드 서버는 포트번호가 서로 달라서 출처가 다르다고 판별이 되고, SOP
정책에서 어긋나기 때문에 서버로부터 응답이 넘어올 때 브라우저에서 CORS Policy 오류를 발생시키게 된다.
cross-origin 요청이 필요한 경우가 많아지고 있기 때문에 이를 안전하게 처리할 정책인 CORS가 나왔다. CORS는 cross-origin 요청을 제한적으로 허용함으로써 좀 더 안전하게 cross-origin 요청을 처리할 수 있는 정책이다.
CORS 요청이 온다면, 서버는 Access-Control-* 헤더를 메시지에 포함시킬 수 있다.
Access-Control-Allow-Origin
: 요청을 허용할 출처를 명시할 때 사용하며, *를 사용하면 모든 출처의 리소스 요청을 허용한다.Access-Control-Allow-Methods
: 어떤 메서드를 허용할 것인지 명시한다.Access-Control-Allow-Headers
: 어떤 헤더들을 허용할 것인지 명시한다.Access-Control-Max-Age
: preflight 요청에 대한 응답을 브라우저에서 얼마만큼 캐싱하고 있을지 설정할 때 사용한다.Access-Control-Expose-Headers
: 브라우저가 스크립트에 노출시킬 헤더의 목록을 명시할 때 사용한다.서버는 위와 같은 헤더를 통해 리소스에 대한 CORS 요청을 어느 수준까지 허용할 것인지 클라이언트(브라우저)에게 알려주고, 이를 바탕으로 브라우저는 추후동작을 결정한다.
CORS 요청을 보낼 때, 브라우저는 해당 요청을 Preflight request
를 사용한 방식과 그렇지 않는 방식(Simple request
) 두 가지 방식으로 처리한다.
GET
, POST
, HEAD
요청XMLHttpRequestUpload
객체에 이벤트 리스너가 등록되지 않은 경우이 방식은 일반 요청이랑 동일하게 동작하며, 한가지 다른 점은 응답 헤더의 Access-Control-Allow-Origin
에 따라 브라우저가 응답을 정상적으로 처리할지를 결정한다.
예를 들어 http://localhost:3000에서 서버(http://localhost:8080/api/hello)에 요청을 보낸다면, 다음과 같은 요청 메시지가 전송된다.
GET /api/hello HTTP/1.1
Host: localhost:8080
Referer: http://localhost:3000/
Origin: http://localhost:3000
...
요청에 서버가 응답을 보낸다.
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 155
...
응답에 Access-Control-Allow-Origin
헤더가 존재하지 않기 때문에 브라우저는 이 응답을 자바스크립트 코드에 노출시키지 않고 CORS 에러를 발생시킨다.
서버에서 Access-Control-Allow-Origin
헤더를 추가해서 응답하면 다음과 같이 정상적으로 자바스크립트 코드에서 응답을 처리할 수 있다.
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:3000
Content-Type: application/json; charset=utf-8
Content-Length: 155
...
Simple request가 아니면 브라우저는 실제 요청을 보내기 전에 preflight request를 실제 요청 메시지보다 서버에 먼저 보낸다.
서버는 이에 대한 응답을 주고, 브라우저는 이를 통해 실제 요청을 보낼지 결정한다.
preflight request는 OPTIONS
메서드를 사용하며, Access-Control-Request-Method
, Access-Control-Request-Headers
, Origin
등의 요청 헤더를 사용한다.
응답이 오면, 브라우저는 위 3가지 헤더 값과 응답의 Access-Control-*
헤더의 값을 비교하며 실제 요청을 서버에게 보낼지 결정한다.
다음과 같은 요청을 보낸다면 Simple request의 조건을 만족하지 못하므로 (OPTIONS 메소드 사용), 실제 요청을 보내기 전에 preflight request를 보낸다.
OPTIONS /api/menus HTTP/1.1
Host: localhost:8080
Access-Control-Request-Method: GET
Access-Control-Request-Headers: content-type
Referer: http://localhost:3000/
Origin: http://localhost:3000
...
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:8080
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: -1
Connection: keep-alive
Content-Length: 0
서버가 위와 같이 응답을 했다면 브라우저는 실제 요청을 보내도 문제가 없다고 판단하여 실제 요청을 서버에 보낸다.