CORS란 Cross-Origin Resource Sharing의 약자로, 교차 출처 자원 공유 라고 번역할 수 있다. MDN에서는 CORS에 대해 이렇게 설명하고 있습니다.
교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제입니다. 웹 애플리케이션은 리소스가 자신의 출처(도메인, 프로토콜, 포트)와 다를 때 교차 출처 HTTP 요청을 실행합니다.
쉽게 풀어서 설명하자면, 서로 다른 서버끼리 리소스를 주고 받는 과정을 의미한다고 볼 수 있습니다. 그런데 웹 개발 과정에서 CORS 허용해주세요
, CORS 허용이 안되어 있어요
, CORS 오류가 나요
이런 이야기를 많이 듣게 되는데요, 이는 기본적으로 웹 브라우저의 기본 정책이 SOP
이기 때문입니다.
SOP는 Same-Origin Policy의 약자로, 다른 출처의 리소스는 허용하지 않고 오직 같은 출처의 리소스만 허용하겠다는 정책입니다. 그래서 특별한 설정 없이는 CORS가 막혀있는 것입니다.
그렇다면 origin, 즉 출처가 정확히 무엇인지를 알아야 하는데요, origin은 scheme
, host
, port
로 이루어진 값입니다. 다음과 같은 URL이 있다고 할게요.
http://localhost:8080
여기서 프로토콜을 나타내는 http://
가 scheme, localhost
가 host, 8080
이 port입니다. 이 세 가지 중 단 하나라도 다르다면 cross-origin으로 판단합니다. 단, 인터넷 익스플로러에서는 origin에 포트가 포함되지 않고, 이는 비표준입니다. 예를 들어 볼까요? 한 PC 위에 3000번 포트에는 리액트를, 8080번 포트에는 스프링부트를 띄웠다고 가정하겠습니다.
그리고 이 두 서버는 서로 api 통신을 주고받도록 설계되어 있습니다. 이 두 서버는 같은 pc 같은 ip 위에 있기 때문에 same-origin일 것 같지만, 사실 cross-origin입니다. 똑같이 http 통신을 하고, 같은 host이지만 port가 다르기 때문이죠. 그래서 SOP를 위반하기 때문에 따로 CORS 설정을 해주지 않는 한 데이터를 주고받을 수 없습니다.
단, 앞서 말했듯이 SOP는 웹 브라우저의 정책
이기 때문에 Postman 등의 프로그램으로 api 요청을 보내는 등 브라우저를 사용하지 않는다면 적용되지 않을 수 있습니다. 또한 인터넷 익스플로러같이 비표준 브라우저의 경우 다른 브라우저들과 조금 다르게 적용될 수 있습니다.
CORS를 이해하고 넘어가는데 꼭 필요한 것이 하나 더 있습니다. 바로 Preflight Request
입니다. preflight request란 브라우저가 HTTP 요청을 보내기에 앞서 말 그대로 사전 요청을 보내서 응답을 신뢰할 수 있는지 검증하는 과정이라고 생각하시면 되는데요, OPTIONS
메서드를 통해 서버에 요청을 보냅니다.
이 때, 요청하려는 메서드 정보를 Access-Control-Request-Method 헤더에, 요청에 담길 헤더 정보를 Access-Control-Request-Headers 헤더에 담습니다.
OPTIONS /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
그러면 서버에서는 이 요청을 받아서 응답을 보내주게 됩니다. 이때 응답에는 서버에서 허용하는 origin, 메서드, 헤더, 그리고 preflight 요청의 캐싱 가능 시간을 담아줘야 합니다.
HTTP/1.1 204 No Content
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
이런 식으로. Access-Control-Allow-Origin에는 허용된 origin 정보를, Access-Control-Allow-Methods에는 허용하는 메서드를, Access-Control-Allow-Header에는 사용 가능한 헤더 목록을, Access-Control-Max-Age에는 현재의 preflight 요청을 브라우저가 캐싱 가능한 최대 시간을 담아서 제공합니다.
위 응답대로라면 https://foo.example
origin에 대해서 POST
, GET
, OPTIONS
메서드를, 그리고 X-PINGOTHER
, Content-Type
헤더를 허용하게 되고 86400ms 동안 preflight 요청을 캐싱할 수 있습니다.
그런데 preflight 요청을 보내지 않는 경우도 있습니다. 우선 앞서 말했듯이 브라우저를 사용하지 않으면 당연히 preflight 요청을 보내지 않겠죠? 브라우저를 쓰더라도 preflight 요청을 보내지 않고 바로 본 요청을 하는 경우가 있는데요, 이를 Simple Request
라 합니다.
simple request가 되는데는 몇 가지 조건이 있는데요,
- 본 요청 메서드가 GET, HEAD, POST 중 하나일 것
- 클라이언트에서 자동으로 넣어주는 헤와 Fetch 표준 정책에서 정의한 CORS-safelisted request header라는 헤더 목록에 들어 있는 헤더 외에 다른 헤더를 수정으로 넣어주지 않았을 것
- Content-Type이
application/x-www-form-urlencoded
,multipart/form-data
,text/plain
중 하나일 것 입니다.
마지막으로, 인증 정보를 담아야 할 경우 또한 CORS가 조금 달라집니다. CORS를 요청할 때 세션이나 쿠키 정보를 요청에 담아야 하는 경우가 있는데요, 이를 Credentialed Request
라 합니다.
만약 서버로 요청을 보낼 때 withCredentials
옵션이 들어간다면, 이에 대한 서버의 응답 헤더는 반드시 Access-Control-Allow-Credentials
값이 true
여야 합니다. true
가 아니라면 정책 위반이 됩니다.
그리고 한 가지 더 제약 조건이 있는데요, credentialed 요청의 경우, Access-Control-Allow-Origin
값이 와일드카드(*)
일 수 없습니다. 반드시 특정 origin을 지정해주어야 합니다.
Q. 모든 브라우저에서 SOP를 적용하고 있나요?
A. 자바스크립트가 도입된 이후의 현대 브라우저는 다 SOP를 기본 정책으로 하고 있습니다. 그러나 설명드렸던 대로 인터넷 익스플로러와 같이 비표준을 지원하는 브라우저의 경우 same-origin
인지 판단하는 규칙은 다를 수 있습니다.
Q. 크롬과 같은 브라우저에서는 왜 SOP를 정책으로 적용했을까요? / 브라우저단에서 클라이언트를 보호하기 위함인데요, 다른 출처의 자원에 접근하면 왜 위험한걸까요?
A. SOP가 없는 상황에서 악의적인 자바스크립트 코드가 심어져 있는 피싱 사이트에 접속했다고 가정하겠습니다. 피싱 사이트에 접속하게 되자 사용자도 모르는 사이 해커가 심어 놓은 특정 포털 사이트로 요청을 보내 개인 정보를 받아오고, 받아온 정보를 해커에게 전송
하는 악의적 자바스크립트가 실행됩니다. 개인정보가 유출된 것이죠. 만약 SOP가 적용되었다면 포털 사이트와 피싱 사이트 사이의 CORS가 허용되어 있지 않기 때문에 개인정보를 받아올 수 없어서 개인정보 유출이 되지 않았을 것입니다. SOP가 적용되지 않는다면 이런 사례의 공격 방식에 당할 수 있습니다.
Q. 서버단에서 allow를 열어주는 것은 어렵지 않은데요 이게 어떻게 보안에 도움이 되는것일까요?
A. 모든 요청에 대해 SOP를 적용해버리면 다른 origin과는 아예 API의 통신을 할 수가 없습니다. 때문에 결국 CORS를 허용해줄 수 밖에 없는데요. 위에서 설명드린 대로, 신뢰할 수 있는 origin에 대해서만 access controll allow를 해서 공격을 막을 수 있습니다. 애초에 악의적인 동작이 없는 사이트에만 allow를 열어주어 악의적 자바스크립트가 실행되더라도 중요한 정보가 넘어가는 것을 방지하는 것입니다.
Q. 허용하지 않은 오리진에서 요청이 오면 서버에서는 어떻게 하나요?
A. 허용하지 않은 origin에서 요청이 오더라도 서버는 정상적으로 응답을 해줍니다. 다만 그 이전 사전 요청(preflight request)에 CORS 관련된 헤더들을 보내주게 되는데, 이 헤더에 담긴 정보와 요청을 보낸 origin 정보가 달라서 브라우저 단에서 응답이 차단되겠죠.