면접에서 CORS 관련 질문이 나왔는데, 평소 알고 있는 것보다 훨씬 대답을 못했다. 나는 면접에서 알고있는 것의 반 정도... 만 이야기를 하는 수준이기 때문에 면접에서 얘기하고자 하는 것의 '두 배!!'를 공부해야한다.
오늘은 나 자신에게 조금 실망을 해서 좀 더 자세하게 정리해야겠다.
CORS : Cross-Origin Resource Sharing의 약자.
직역해보자면 교차 출처 리소스 공유
Origin에 대해 쓴 내 블로그
즉, CORS는 다른 origin에서의 리소스 요청을 허용하되, 이걸 가능하게 하려면 보안상의 이슈가 생기기 때문에 그 보안을 위해 만든 '기준'이 되겠다.
SOP(Same-Origin Policy)의 예외 조항이라고 생각해도 되겠다.
중요!
이 출처를 비교하는 로직은 브라우저에 구현되어 있는 스펙이다. 즉, 서버에서 같은 출처에서 보낸 요청만 받겠다는 로직이 구현되어 있지 않다면 서버는 정상적으로 응답을 하게 된다. 하지만 클라이언트가 이 응답을 분석해서 CORS 정책을 위반했을 때는 해당 응답을 버리게 된다.
기본적으로 브라우저는 HTTP의 요청 헤더에 Origin 필드에 출처를 담아 보낸다.
이후 서버가 응답에 Access-Control-Allow-Origin
이라는 값에 허용된 출처를 같이 보내고, 브라우저는 이것을 통해 응답이 유효한 응답인지 아닌지를 결정한다.
Post
요청을 보냈을 때, 항상 봤던 요청이다.
아래와 헤더들을 갖고있다.
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
Preflight 요청 이후에 보낼 요청들에 대한 정보들을 함께 담고 있다는 것을 알 수 있다. (예를 들어 다음 요청이 Post
요청이라는 것을 알려주고 있다.)
다음은 응답 코드다.
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
에 'POST'가 빠져있었다면 브라우저에서는 CORS 에러를 볼 수 있다.
Preflight 요청 없이 진행되는 요청이다.
특정 조건을 만족시키면 단순 요청으로 보내지며, preflight을 보내지 않는다.
MDN 설명
fetch API나 axios에서 credentials 옵션을 건드리면 쿠키 정보 등을 헤더에 담아서 보낸다.
프로젝트를 할 때 몇 번 확인했던 오류다.
그런데 이렇게 하면 다음과 같은 오류가 뜰때가 있다.
Access to fetch at ’http://foo.example’ from origin ’http://bar.example’ has been blocked by CORS policy: The value of the ‘Access-Control-Allow-Origin’ header in the response must not be the wildcard ’*’ when the request’s credentials mode is ‘include’.
개발 초기에는 대부분 Access-Control-Allow-Origin
에 wildcard '*'를 할당해서 썼다. 그런데 인증 정보를 포함한 요청을 보내기 시작하면 이게 문제가 되는 것이다.
따라서 이때는 명시적인 URL을 지정해주어야 했다.