2023-05-16 22:16:35

CORS(Cross-Origin Resource Sharing)에 대해서 자료를 살펴보던 중에 preflight request라는 용어를 접하게 되어 내용을 정리해봤습니다. 

 

CORS란?

우선 CORS에 대해서 설명이 필요하겠죠? 

 

CORS, Cross-Origin Resource sharing은 말 그대로 Origin이 다른 경우에도 리소스를 공유할 수 있게 허용해주는 정책을 뜻합니다. 여기서 Origin은 프로토콜 + 호스트 + 포트를 합한 것으로 이 세가지가 동일하면 같은 Origin이라고 판단합니다. 예를 들어, http://123.456.789.123:3000과 http://123.456.789.123:8001은 다른 Origin입니다. 

 

프론트엔드와 백엔드가 딱히 분리되어 있지 않던 시절에는 프론트와 백엔드가 같은 Origin에 있었습니다. 하지만 백엔드와 프론트엔드가 분리되어 각기 다른 서버에 존재하기 시작하면서 문제가 발생하기 시작했습니다. 별도로 존재하는 프론트엔드 서버에서 백엔드 서버로 요청(request)이 이뤄져야 하는 상황이 되었습니다. 동시에 어떤 곳에서 백엔드 서버로 요청이 올지 이제는 알 수 없어진 것이죠. 따라서, 백엔드 서버 쪽에는 어떤 Origin에서 요청을 했을 때 받아줄 것인지 허용 Origin에 대해서 정의해줄 필요가 생겼습니다.

 

백엔드 서버: "나는 이 Origin에서 이뤄지는 요청만 처리해줄거야. 다른 Origin은 믿을 수 없어. 해커가 날 공격할 수도 있잖아?"  

 

그런데 일단 프론트엔드에서 백엔드 쪽으로 요청을 보낸 이후에만 허용된 Origin인지 아닌지를 알 수 있는 상황이어서 문제가 되었습니다. 애초에 요청 자체를 못하게 막을 방법이 필요했습니다. 그래서 나온 것이 바로 preflight request입니다. 

 

preflight request란? 

preflight request는 실제 요청 전에 브라우저에서 보내는 작은 요청입니다. 지금 요청을 보내는 프론트엔드가 백엔드 서버에서 허용한 Origin이 맞는지, 그리고 해당 엔드포인트에서 어떤 HTTP 메소드들을 허용하는지 등을 확인합니다. 만약 허용되는 Origin이고 요청하는 메소드도 허용되는 것이라면 실제 요청을 할 수 있게 해줍니다. 그렇지 않다면, 실제 요청을 보내기도 전에 보내지 못하게 막는 것이죠. 아래 그림을 보시면 Preflight request가 무엇인지 감을 잡으실 수 있을 것입니다. 

 

이미지 출처: [1]

 

preflight request가 이뤄지려면 서버에서 OPTIONS 메서드를 허용해줘야 합니다. preflight request는 OPTIONS 메서드에 의해 만들어지기 때문입니다. 

 

CORS 작동 방식

위에서 언급한 preflight request는 CORS의 작동 방식 중 하나입니다. CORS 작동 방식은 아래 3가지와 같은 것들이 있습니다. 인파님의 글[4]을 참고하여 정리했습니다. 

 

1. preflight request 

요청을 바로 보내지 않고 preflight request를 보내서 허용되는 메소드가 무엇인지, 허용되는 Origin인지 등을 먼저 확인합니다. 브라우저와 서버는 다음과 같은 방식으로 통신합니다. 

 

1) 브라우저는 서버로 HTTP OPTIONS 메소드로 preflight request를 보냅니다.

* Origin 헤더에 자신의 출처를 넣습니다.

* Access-Control-Request-Method 헤더에 실제 요청에 사용할 메소드를 설정합니다.

* Access-Control-Request-Headers 헤더에 실제 요청에 사용할 헤더들을 설정합니다. 

 

2) 서버는 이 preflight request에 대한 응답으로 허용되는 것들에 대한 정보를 헤더에 담아서 브라우저로 보냅니다. 

* Access-Control-Allow-Origin 헤더에 허용되는 Origin들을 알려줍니다.

* Access-Control-Allow-Methods 헤더에 허용되는 메소드들을 알려줍니다. 

* Access-Control-Allow-Headers 헤더에 허용되는 헤더들을 알려줍니다.  

* Access-Control-Max-Age 헤더에 해당 preflight request가 브라우저에 캐시 될 수 있는 시간을 초 단위로 알려줍니다. 

 

3) 브라우저는 preflight request/response를 통해 본 요청이 이뤄질 수 있는지 미리 확인한 후 본 요청을 보냅니다. 

4) 서버는 본 요청에 대해 응답해줍니다.   

 

2. simple request

preflight request를 생략하고 서버에 바로 실제 요청을 보낸 후 서버가 이에 대한 응답의 헤더에 Access-Control-Allow-Origin 헤더를 보내주면 브라우저가 CORS 정책 위반 여부를 검사하는 방식입니다. preflight 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 중 하나여야 합니다. 

 

기본적으로 요즘 HTTP 요청은 application/json 또는 text/xml로 이뤄지기 때문에 대부분 3번째 Content-Type 헤더 조건을 만족시키지 못합니다. 따라서, preflight request가 이뤄지는 경우가 대다수라고 볼 수 있습니다. 

 

3. credentialed request 

클라이언트가 서버에 요청할 때 자격 인증 정보(Credential)를 담아서 요청할 때 사용되는 방식입니다. 여기서 말하는 자격 인증 정보는 세션 ID가 저장되어 있는 쿠키나 Authorization 헤더에 설정하는 토큰 값 등을 의미합니다.

 

기본적으로 브라우저가 제공하는 요청 API 들은 별도의 옵션 없이 브라우저의 쿠키와 같은 데이터를 함부로 요청 데이터에 담지 않도록 설정되어 있습니다. 요청에 인증과 관련된 정보를 담을 수 있게 해주는 옵션이 바로 credentials 옵션입니다. 이 옵션에는 3가지 값이 있는데 각각 다음과 같은 의미를 같습니다.

 

same-origin: 같은 출처 간 요청에만 인증 정보를 담을 수 있음

include: 모든 요청에 인증 정보를 담을 수 있음

omit: 모든 요청에 인증 정보를 담을 수 없음 

 

credentials가 include로 설정되어야 Cross origin 요청이 가능하겠죠? 

 

또한 서버에서도 응답 헤더를 다음과 같이 설정해줘야 합니다. 

1) 응답 헤더의 Access-Control-Allow-Credentials 항목을 true로 설정합니다. 

2) 응답 헤더의 Access-Control-Allow-Origin을 *(와일드카드)로 설정하면 안 됩니다.

3) 응답 헤더의 Access-Control-Allow-Methods를 *로 설정하면 안 됩니다.

4) 응답 헤더의 Access-Control-Allow-Headers를 *로 설정하면 안 됩니다. 

 

참고로 credentialed request 역시 preflight request가 선행됩니다. 

 

주의할 점

origin 체크는 서버가 아니라 브라우저에서 이뤄진다는 점입니다. preflight request가 없이 실제 요청으로 바로 넘어가는 경우에는 이미 서버 쪽에서는 브라우저 쪽의 요청에 의해 어떠한 작업이 일어난 후에 CORS 에러가 브라우저 쪽에서 보여질 수도 있습니다. 굉장히 조심해야 할 상황입니다. CORS 에러가 떴으니 요청이 서버 쪽에 안 먹혔겠지 생각하고 있다가는 큰 코 다칠 수 있는 상황입니다. 서버는 요청에 대해 응답을 반환했을 수 있습니다. 다만 브라우저에서 그 응답을 받을 수 없는 상황일 수 있습니다. 

 

정리하며

제 이해가 완벽하지 않을 수 있습니다. 혹시 잘못된 부분이 있다면 가감없이 지적해주시고 설명해주신다면 정말 감사하겠습니다. 

 

참고자료

[1] https://livebook.manning.com/book/cors-in-action/chapter-4/13  

[2] https://wonit.tistory.com/571 

[3] https://hanamon.kr/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-http-options-%EB%A9%94%EC%86%8C%EB%93%9C%EB%A5%BC-%EC%93%B0%EB%8A%94-%EC%9D%B4%EC%9C%A0%EC%99%80-cors%EB%9E%80/   

[4] https://inpa.tistory.com/entry/WEB-%F0%9F%93%9A-CORS-%F0%9F%92%AF-%EC%A0%95%EB%A6%AC-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95-%F0%9F%91%8F  

[5] https://www.youtube.com/watch?v=-2TgkKYmJt4