October 17, 2021
CORS는 다른 Origin으로 요청을 보내기 위해 지켜야 하는 브라우저의 정책으로, 원래대로라면 SOP에 의해 막히게 될 요청을 풀어주는 역할을 한다.
웹 브라우저에서 다른 origin을 가진 서버로의 자원 요청을 안전하게 보낼 수 있게 하기 위해 등장한 방식이 바로 CORS이다.
이를 위해서는 서버에서 Access-Control-Allow-Origin 헤더에 클라이언트의 origin을 명시해주어야 한다.
즉, 쉽게 해석해보면 아래와 같다.
다른 origin 간 자원 공유 허용해줄게.
근데 위험하니까 몇 가지 규칙을 지킨 요청에 대해서만 허용해줄게.
그 규칙들에는 다음과 같은 것들이 있다.
만약 credentials(Cookie 혹은 HTTP Authorization 헤더에 설정하는 토큰 값 같은 인증 정보)를 포함하는 요청을 보내고 응답받고자 한다면
Access-Control-Allow-Credentials: true
헤더를 응답에 포함하여 보내주어야 한다. 이때 Access-Control-Allow-Origin
헤더의 값으로 *
는 사용할 수 없다.withCredentials: true
/ fetch -> credentials: 'include'
)CORS는 브라우저의 정책인데 서버에서 조치를 취해줘야 한다는 게 아이러니하지만 어쨌든 그렇다.
안전하지 않은 요청, 즉 Simple Request의 조건에 벗어나는 요청)의 경우, 본 요청 전에 브라우저가 OPTIONS 메서드를 이용하여 preflight 요청을 먼저 보내서 실제 요청이 전송하기에 안전한지 확인한다. (브라우저가 알아서 보낸다. FE 개발자가 따로 처리해줄 필요는 없다.)
preflight 요청을 통해 안전한 요청이라고 확인된다면 그때서야 서버에게 본 요청을 보낸다.
우리가 자바스크립트의 fetch API를 사용하여 브라우저에게 리소스를 받아오라는 명령을 내리면 브라우저는 서버에게 예비 요청을 먼저 보내고, 서버는 이 예비 요청에 대한 응답으로 현재 자신이 어떤 것들을 허용하고, 어떤 것들을 금지하고 있는지에 대한 정보를 응답 헤더에 담아서 브라우저에게 다시 보내주게 된다.
이후 브라우저는 자신이 보낸 예비 요청과 서버가 응답에 담아준 허용 정책을 비교한 후, 이 요청을 보내는 것이 안전하다고 판단되면 같은 엔드포인트로 다시 본 요청을 보내게 된다. 이후 서버가 이 본 요청에 대한 응답을 하면 브라우저는 최종적으로 이 응답 데이터를 자바스크립트에게 넘겨준다.
Q. 서버의 응답 헤더에 요청을 보낸 클라이언트의 origin이 명시되어 있지 않다면 본 요청만 보내도 어차피 거절이 될 텐데 이처럼 preflight 요청을 보내 미리 확인하는 이유는 뭘까?
만약 서버의 Access-Control-Allow-Origin 헤더에 명시돼있지 않은 클라이언트가 DB에 있는 정보를 수정 혹은 삭제하는 PATCH 혹은 DELETE 요청을 서버에 전송했다고 가정해보자. 이 같은 요청은 일단 서버에게 간다. 그리고 서버는 그 요청을 처리한다. 이후 브라우저가 응답을 받고 나서 서버의 응답 헤더를 보고 CORS 에러를 발생시킨다.
앞에서도 말했듯이, CORS는 브라우저의 정책이다. 즉, 서버는 모른다. 그러므로 서버는 일단 어느 요청이든 일단 처리를 한다. 그리고 응답한다. 그 응답을 브라우저가 받아서 클라이언트에게 전달할지 말지를 결정하는 것은 브라우저(의 CORS 정책)이다.
서버는 CORS 정책을 모르기 때문에 일단 요청을 처리한다는 것이 문제이다. 그렇기에 Preflight 요청을 하는 것이다. Preflight 요청은 사전 요청이기 때문에 서버는 어떠한 행동도 취하지 않는다.
CORS와 관련해 더 자세한 내용은 [MDN] CORS에서 확인하는 것을 추천한다.
브라우저는 보안상의 이유로 <script>
태그 내에서는 다른 origin 간 HTTP 요청을 보낼 수 없게 한다.
즉, SOP(Same Origin Policy)를 따른다.
SOP
는 웹 브라우저가 보안을 위해 Same-Origin의 서버로만 리소스를 주고받도록 제한하는 정책을 의미한다.
그러나 모든 HTTP 요청에 SOP가 적용되는 것은 아니다.
<script src="…"></script>
<link rel="stylesheet" href="…">
<img>
<video>
and <audio>
등위에서 CORS의 정의를 설명하며 언급한 원래대로라면 SOP에 의해 막히게 될 요청이 SOP가 적용되는 HTTP 요청들을 의미한다.
SOP는 Browser의 정책으로, Server to Server 통신에서는 적용되지 않는다. 즉, 서로 다른 origin이어도 얼마든지 통신이 가능하다.
이는 매우 중요하다. CORS 에러를 Proxy를 통해 클라이언트 단에서 해결할 수 있음을 이해하기 위해서도 필수적으로 알아야 하는 내용이다.
외부 API 서버를 사용하는 등의 이유로 응답 서버를 제어할 수 없을 때 CORS 외의 대안으로 Proxy 서버를 이용할 수 있다. (연결되는 링크의 포스팅에서 Proxy를 이용해 CORS 에러를 해결하는 방법에 대해서 다룬다.)
CORS 정책이 생기기 이전에 SOP 정책을 우회하던 방법 -> JSONP (현재는 보안상의 이슈로 잘 사용하지 않는다.)