CORS는 다른 출처(교차 출처)로의 리소스 요청을 허용해주는 정책이다.
SOP가 존재하기 때문에 기본적으로 다른 출처의 리소스를 요청하는 것은 차단된다. 하지만 웹이 발전하고 커지면서 다른 출처의 리소스를 요청하는 일이 흔하고 일반적이게 되었고 다른 출처의 리소스를 요청하는 것을 무작정 차단할 수 없는 상황이 되었다.
이에 따라 몇 가지 예외 조항을 두고 이 조항에 맞는 다른 출처로의 리소스 요청을 허용하게 되었다. 이 예외 조항을 따르는 다른 출처로의 리소스 요청이 바로 CORS 정책을 지킨 리소스 요청이다.
다른 출처로의 리소스 요청이더라도 CORS 정책을 지켰다면 요청이 허용된다.
브라우저에서 다른 출처로의 리소스 요청을 보낼 때 브라우저는 요청 헤더에 Origin
필드를 추가하고, 이 필드에 요청을 보내는 출처를 담는다.
서버가 이 요청에 대한 응답을 보낼 때 응답 헤더에 Access-Controll-Allow-Origin
필드를 추가하고, 이 필드에 해당 리소스를 요청할 수 있는 출처를 담는다.
서버의 응답을 받은 브라우저는 서버가 보낸 응답 헤더의 Access-Control-Allow-Origin
필드 값을 확인해 요청을 보내는 출처를 담은 Origin
필드 값과 비교하여 서버의 응답이 유효한지 아닌지를 결정한다.
CORS는 기본적으로 위와 같이 동작한다. 사실 CORS에는 3가지 동작 방식이 존재하는데, 시나리오에 따라 동작 방식이 3가지로 나뉘어서 동작한다.
Preflight Request
는 CORS의 다른 두 가지 시나리오가 아닌 경우의 요청 시나리오로, 가장 일반적인 시나리오이다.
Preflight
방식의 요청인 경우 브라우저는 스스로 요청을 두 단계로 나눠서 보낸다.
Preflight 요청
Preflight
요청은 실제 요청을 보낼 수 있는지 판단하기 위해 보내는 확인 요청이다. 브라우저는 개발자 의도와는 상관 없이 preflight 요청을 스스로 먼저 보내고 이에 대한 서버의 preflight 응답을 받아 실제 요청을 보내는 것이 가능한지를 판단한다.
브라우저는 preflight 요청을 통해 실제 요청을 보내는 것이 가능하다고 판단했을 때 실제 요청을 서버에 보낸다.
브라우저가 보내는 preflight 요청에는 다음과 같은 특징이 있다.
OPTIONS
메서드를 사용한다.Origin
: 요청을 보내는 출처Access-Control-Request-Method
: 실제 요청 시 사용할 메서드Access-Control-Request-Headers
: 실제 요청 시 사용할 헤더들서버가 브라우저의 preflight 요청에 응답하는 preflight 응답에는 다음과 같은 특징이 있다.
Access-Control-Allow-Origin
: 리소스 요청이 가능한 출처Access-Control-Allow-Methods
: 실제 요청 시 사용 가능한 메서드Access-Control-Allow-Headers
: 실제 요청 시 사용 가능한 헤더들Access-Control-Max-Age
: preflight 응답의 캐시 시간브라우저는 서버의 preflight 응답 헤더에 포함된 Access-Control-Allow-Origin
필드의 값을 확인해 실제 요청이 가능한지를 판단한다.
Origin
필드의 값)와 다르다면 브라우저는 이 요청이 CORS 정책을 위반했다고 판단하여 CORS 정책에 위반했다는 에러를 출력하게 된다.preflight 요청에 대한 응답을 통해 브라우저는 실제 요청 가능 여부를 판단하고 CORS 정책에 위반되지 않았다면 실제 요청을 보내고 CORS 정책에 위반되었다면 CORS 에러를 출력하게 된다.
단순 요청
은 preflight 요청 없이 실제 요청을 서버에게 바로 보내는 시나리오이다.
단순 요청에는 특정 조건이 필요한데, 아래 3가지 조건을 만족하면 브라우저는 단순 요청을 보낸다.
GET
, POST
, HEAD
중 하나Aceept
, Accept-Language
, Content-Language
, Content-Type
만 가능application/x-www-form-urlencoded
, multipart/form-data
, text/plain
만 가능이 조건을 만족하지 않으면 단순 요청을 보내지 않는다. (실제로는 이 조건을 만족하는 요청이 거의 없다고 한다.)
서버가 브라우저의 단순 요청에 대한 응답 헤더에 Access-Control-Allow-Origin
필드를 더해 응답을 보내주면 브라우저는 이 필드의 값을 통해 CORS 정책에 위반되었는지를 확인한다.
Access-Control-Allow-Origin
필드 값을 확인하는 것과 동일하게 확인한다.단순 요청처럼 preflight 요청과 응답 없이도 CORS 정책 위반 여부를 확인할 수 있는데, 굳이 preflight 요청과 응답 과정을 진행하는 이유가 무엇일까.
그 이유는 CORS를 모르는 서버가 존재하기 때문이라고 한다.
preflight 없이 다른 출처에서 리소스를 요청하는 경우
Access-Control-Allow-Origin
필드가 없다.Access-Control-Allow-Origin
필드를 통해 CORS 정책 위반 여부를 확인하는데, Access-Control-Allow-Origin
필드가 없으므로 CORS 정책을 위반했다고 판단하여 CORS 에러를 출력한다.CORS 정책을 위반한 요청임에도 불구하고 서버는 일단 그 요청을 모두 처리한다. 만약 이 요청이 리소스를 삭제하거나 수정하는 요청이라면 서버는 DB에서 관련 요청에 대한 데이터를 삭제하거나 수정하게 된다.
이런 이유 때문에 preflight 요청과 응답을 통해 CORS 정책을 위반하는지를 실제 요청을 보내기 전에 판단하는 것이다. 서버가 CORS 관련 설정을 처리하지 않으면 응답 헤더에는 Access-Control-Allow-Origin
필드가 없기 때문에 브라우저는 CORS 정책을 위반했다고 판단하여 실제 요청을 보내지 않는다.
Credentialed Request
는 인증 관련 헤더를 포함할 때 사용하는 요청이다.
기본적으로 브라우저는 쿠키 정보나 인증 관련 헤더를 다른 출처로의 리소스 요청에 담지 않는다. 이때 요청에 인증 관련 정보를 담을 수 있게 해주는 옵션이 credentials
라는 옵션이다.
credentials 옵션 값들
same-origin
(디폴트): 동일 출처로의 요청에만 인증 정보를 담을 수 있다.include
: 모든 요청에 인증 정보를 담을 수 있다.omit
: 모든 요청에 인증 정보를 담지 않는다.다른 출처로의 리소스 요청을 보낼 때 인증 정보를 함께 전달하기 위해 credentials: include
로 설정한 경우 브라우저는 CORS 정책 위반 여부를 검사할 때 2가지 규칙을 추가한다.
Access-Control-Allow-Origin
필드에 *
을 사용할 수 없고, 명시적인 URL이 있어야 한다.Access-Control-Allow-Credentials: true
가 응답 헤더에 존재해야 한다.credentials: include
옵션을 사용하면 브라우저는 위 2가지 규칙을 CORS 정책 위반 여부를 검사할 때 추가로 확인하고, CORS 정책을 위반했다면 CORS 에러를 출력한다.