지난 앱잼 때 나를 지독하게도 괴롭혔던...CORS정책에 대해 알아보자.
CORS정책에 대해 알기 위해서는 먼저 SOP에 대해서 알아야 한다.
1. SOP
Same-Origin Policy의 약자이다. 다른 출처의 리소스를 사용하는 것을 제한하는 정책 방식이다. 클라이언트는 매우 취약한 환경에 놓여있기 때문에 보안에 신경써야 할 필요가 있다. 당장 개발자 도구만 클릭해서 보더라도 어떤 형식으로 구현되어 있는지 다 알 수 있으니.. 하지만 오픈스페이스 환경에서 어떻게 동일 출처의 자원만 사용할 수 있겠는가..따라서 예외 조항을 신설하고, CORS 정책을 지킨 리소스 요청은 허용하기로 했다.
2. Origin
그럼, '출처'는 어떻게 파악할까?
프로토콜, 호스트, 포트를 통해 출처를 판단한다.
이 때 포트번호는 생략이 가능하다.
참고로 IE는 포트가 달라도 동일한 출처로 인식한다고 한다(IE를 쓰지 말아야 하는 이유..)
기억해야 할 것은, 이렇게 포트를 비교하는 과정이 서버가 아니라 브라우저에서 한다는 것이다.
즉, 요청이 들어오면 서버에서는 정상적으로 로직을 수행해서 response를 보낸다. 하지만 브라우저에서 출처를 비교해서 정상적인 response를 반환하지 않는것....
3. CORS의 동작원리
1) 요청헤더의 Origin 필드에 출처를 함께 담아서 보낸다.
2) 이후 서버가 해당 요청에 대한 응답을 할 때 응답 헤더의 Access-Control-Allow-Origin이라는 값에 '이 리소스를 접근하는 것이 허용된 출처'를 보낸다.
3) 브라우저는 자신이 보냈던 요청의 Origin의 값과 서버 응답 헤더의 값과 비교한 후 유효한 응답인지 확인한다.
Preflight Request
우리가 자주 접하는 시나리오이다. '예비 요청'이라는 말인데, 본요청을 보내기 전 options메소드로 origin을 같이 보낸다. 브라우저 스스로 해당 요청이 안전한지 확인하는 것이다.
웹에서 fetch를 사용하면 서버는 이에 대한 응답으로 어떤 origin이 금지, 허용되는지 알려주는 것이다.
만약 이 요청이 안전하다고 판단될 경우, 클라이언트는 본요청을 다시 보내게 된다.
Preflight Request는 왜 필요할까?
앞에서도 언급했듯 서버는 CORS정책을 위반했는지 안 했는지 알 길이 없다. 하지만 만약 본요청이 DELETE라면..?
서버에서는 해당 데이터를 지워서 정상적인 200 code를 반환했지만, 브라우저는 에러를 내뱉는다. 그것도 모르고 클라이언트가 동일한 요청을 계속 보낸다면...DB가 망가질 것이다. 따라서 먼저 안전한지 확인하는 것이다!
Simple Request
바로 본요청을 보내는 것이다. 하지만 이러한 요청은 몇 가지 까다로운 제약 조건이 있다.
1) 요청 메소드는 GET, HEAD, POST로 제한된다.
2) Accept, Accept-Language, Content-Language, Content-Type, DPR, Downlink, Save-Data, Viewport-Width, Width를 제외한 헤더는 사용 불가하다. 즉, 커스텀 헤더의 사용이 불가하다.
3) Content-Type에는 application/x-www-form-urlencoded, multipart/form-data, text/plain만 허용된다.(json 사용 불가..)
Credentialed Request
인증된 요청을 사용하는 방법이다. 즉, 보안을 조금 더 강화시킨 요청 방식이라 할 수 있겠다.
제약조건은,
1) Access-Control-Allow-Origin에는 마스터키인 *을 사용할 수 없고 반드시 명시적인 url이어야 한다.
2) 응답 헤더에는 반드시 Access-Control-Allow-Crendentials: true 가 있어야 한다.
4. CORS 해결?
1) Access-Control-Allow-Origin을 세팅하면 된다. 토이 프로젝트에서는 마스터키인 *을 주로 사용하는 편인데, 사실 이렇게 되면 보안상 좋지는 않다.
2) 리버스 프록싱
프록시가 클라이언트의 주소를 가려주기 때문에 서버는 동일 출처로 인식하게 된다.