CORS(Cross-Origin Resource Sharing) 란? | cors가 필요한 이유, 동작 원리, preflight, 에러 해결법
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를 요청할 때 브라우저와 서버의 동작을 나타내는 그림
단순 요청은 서버에 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
서버에 예비 요청을 보내서 안전한지 판단한 후 본 요청을 보내는 방법
Preflight 요청 동작을 나타내는 그림
실제 리소스를 요청하기 전에 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을 통해 특정 브라우저가 리소스에 접근이 가능하도록 허용할 수 있습니다.