CORS(Cross-Origin Resource Sharing) 란?
- 서로 다른 출처(origin)간에 리소스를 공유하는 것을 허용하는 정책
- 기본적으로 차단되어 있다.
브라우저는 기본적으로 서로 다른 출처(origin)에 대해서 공유를 제한하는 SOP을 따른다.
SOP(Same-Origin Policy)이란 같은 출처끼리만 리소스를 공유할 수 있다는 정책이다.
* HTML태그를 통한 이미지, CSS, Script 요청은 SOP에 의해 제한되지 않음
💡 origin: 출처를 의미하며, URL 구조에서 Protocol+Host+Port를 합친 것을 말함
* port가 다를 경우 다른 출처로 인식
* 80(HTTP), 443(HTTPS)번 포트는 생략 가능
CORS 가 정의된 이유
SOP(Same-Origin Policy)의 장점
동일 출처 정책을 지키면 외부 리소스를 가져오지 못해 불편하지만, 동일 출처 정책은 XSS나 XSRF(=CSRF) 등의 보안 취약점을 노린 공격을 방어할 수 있다.
출처에 대한 비교는 브라우저에서 이루어지게 된다. 즉, 브라우저에서 SOP는 브라우저의 쿠키, 세션, 로컬스토리지 등에 사용자의 정보를 저장할 수 있기 때문에 다른 출처에 리소스를 공유하게 되면 보안상 취약해질 수 있다는 문제점 때문에 생긴 정책이다.
SOP(Same-Origin Policy)의 한계
하지만 현실적으로는 외부 리소스를 참고하는 것은 필요하기 때문에 외부 리소스를 가져올 수 있는 방법이 존재해야 한다. 외부 리소스를 사용하기 위한 SOP의 예외 조항이 CORS이다.
CORS 의 동작 원리
서버와 통신을 할 때 응답을 받는 브라우저에서 요청 헤더의 Origin필드와 응답 헤더의 Access-Control-Allow-Origin 필드를 비교하여 응답의 유효성을 판단한다.
즉, 요청을 보내는 쪽에서 요청 헤더에 리소스를 필요로하는 URL을 Origin 필드에 담아서 보내고, 응답을 보내는 서버 쪽에선 Access-Contrl-Allow-Origin 필드에 리소스 접근이 허용된 출처를 담아서 보내 브라우저가 이를 비교하는 것이다.
구체적인 요청 방법으로는 예비 요청을 보내는 preflight request, 예비 요청 없이 본 요청을 바로 보내는 simple request, 인증 정보를 담아서 보내는 credentialed request가 존재한다.
1. Simple request
단순 요청 방법은 서버에게 바로 요청을 보내는 방법
단순 요청은 서버에 API를 요청하고, 서버는 Access-Control-Allow-Origin
헤더를 포함한 응답을 브라우저에 보낸다. 브라우저는 Access-Control-Allow-Origin
헤더를 확인해서 CORS 동작을 수행할지 판단한다.
Simple request 조건
단순 요청으로 동작하기 위해서 서버로 전달하는 요청(request)이 만족해야 하는 조건 3가지
- 요청 메서드(method)는
GET
,HEAD
,POST
중 하나여야 합니다. Accept
,Accept-Language
,Content-Language
,Content-Type
,DPR
,Downlink
,Save-Data
,Viewport-Width
,Width
외의 헤더를 사용하면 안 됩니다.Content-Type
헤더는application/x-www-form-urlencoded
,multipart/form-data
,text/plain
중 하나를 사용해야 합니다.
⇒ 2: Authorization
헤더를 포함할 수 없고, 3: application/json
을 사용할 수 없어서 지키기 어렵 (많은 REST API가 Content type 으로 application/json 을 사용하기 때문)
2. Preflight request
서버에 예비 요청을 보내서 안전한지 판단한 후 본 요청을 보내는 방법
실제 리소스를 요청하기 전에 OPTIONS라는 메서드를 통해 실제 요청을 전송할지 판단한다.
OPTIONS
메서드로 서버에 예비 요청을 먼저 보내고, 서버는 이 예비 요청에 대한 응답으로 Access-Control-Allow-Origin
헤더를 포함한 응답을 브라우저에 보낸다. 브라우저는 단순 요청과 동일하게
Access-Control-Allow-Origin
헤더를 확인해서 CORS 동작을 수행할지 판단한다.
Preflight 를 날리는 이유는?
브라우저가 CORS를 지원하지 않은 서버에 도달하면 요청에 대한 응답을 보내지 않아 실제 요청이 수행되지 않도록 보호하기 위해서 만들어졌다.
예를 들어, POST 요청을 할 때, preflight가 없다면 브라우저가 CORS를 관리하기 때문에 CORS의 여부에 상관없이 이미 서버에서는 요청한 로직이 수행이 된 응답 값이 브라우저에 도달하게 된다. 하지만 CORS 에러로 인해서 브라우저는 그 사실을 알지 못하게 되고 치명적인 서버의 오류로 남아있을 수 있다. 따라서 이를 방지하고자 preflight request 를 보내는 것이다.
3. Credentialed Request
인증된 요청을 사용하는 방법
- CORS의 기본적인 방식이라기 보단 다른 출처간 통신에서 좀 더 보안을 강화하고 싶을 때 사용하는 방법
- 기본적으로 브라우저가 제공하는 비동기 리소스 요청 API인 XMLHttpRequest 객체나 fetch API는 별도의 옵션 없이 브라우저의 쿠키 정보나 인증과 관련된 헤더를 함부로 요청에 담지 않는다.
- 이때 요청에 인증과 관련된 정보를 담을 수 있게 해주는 옵션이 바로 credentials 옵션이다.
same-origin
(기본값) : 같은 출처 간 요청에만 인증 정보를 담는다.include
: 모든 요청에 인증 정보를 담는다.omit
: 모든 요청에 인증 정보를 담지 않는다.
- credentials 옵션
same-origin이나 include와 같은 옵션을 사용하여 리소스 요청에 인증 정보가 포함된다면, 브라우저는 단순히 CORS 정책 위반 여부 검사(Access-Control-Allow-Origin) 확인과 더불어 추가 검사를 하게 된다.
Access-Control-Allow-Origin에는 *를 사용할 수 없으며, 명시적인 URL이어야함
응답 헤더에는 반드시 Access-Control-Allow-Credentials: true가 존재해야함
[참고] 왜 Postman에서는 Cors가 발생되지 않을까?
서버를 테스트하기 위해 Postman에서 요청을 실행할땐 문제없이 잘 동작하다가, 서버를 띄우고 브라우저에서 통신을 하는 순간 Cors 에러를 직면한 경험이 다들 있을 것이다.
원인은 바로 "브라우저"에 있다.
💡 CORS 정책 위반 판단 검사를 시행하는 주체는 “브라우저” 이다.
💡 따라서, 브라우저 없이 서버 간 통신을 진행하는 Postman 에서는 Cors 에러를 볼 수 없는 것이다.
CORS 에러 해결 방법
서버에서 응답 헤더에 특정 헤더를 포함하는 방식으로 해결할 수 있다.
Access-Control-Allow-Origin
: 특정 origin이 리소스에 접근이 가능하도록 허용합니다.Access-Control-Allow-Method
: 특정 HTTP Method만 리소스에 접근이 가능하도록 허용합니다.Access-Control-Expose-Headers
: 자바스크립트에서 헤더에 접근할 수 있도록 허용합니다.credentials
: 쿠키 등의 인증 정보를 전달할 수 있습니다.
정리
- CORS란 서로 다른
Origin
간에 자원을 공유하는 정책을 말하며 기본적으로 브라우저간에 차단되어있습니다. - Origin이란 출처를 말하며,
Protocol + Host + Port
를 합친 것을 의미합니다. - CORS 에러는 서버에서 응답 헤더에 특정 헤더를 포함하는 방식으로 해결할 수 있습니다.
- 예를 들어
Access-Control-Allow-Origin
을 통해 특정 브라우저가 리소스에 접근이 가능하도록 허용할 수 있습니다.
- 예를 들어
참고 자료