CORS(Cross Origin Resource Sharing)
CORS란?
교차 출처 리소스 공유 (Cross-origin resource sharing)의 약자입니다.
현재 브라우저로 접속중인 페이지에서 자바스크립트를 이용해 다른 도메인 또는 포트를 가진 주소로 요청을 하는 경우, 해당 리소스에 접근을 허용했는지 확인해 보안을 높이는 동작을 CORS라고 부릅니다.
CORS 문제가 발생하는 이유
간략하게 예를들면
프론트의 도메인은 example.com:8080, 백엔드의 도메인은 example.com:8000 이라고 가정한고 진행합니다.
- 프론트에서 백으로 api요청을 진행
- 백엔드 서버에서 다른 도메인임을 감지 (포트번호가 다르기때문에 다른 도메인으로 취급)
- CORS 허용 여부에 따라 프론트엔드의 요청을 허용하거나 거부함.
- 브라우저는 주소(도메인, 포트 중 하나라도)가 다른 경우 리소스의 출처가 다르다고 판단합니다.
- 브라우저에서 출처가 다르다고 판단하면 어떻게 동작하는지
- 브라우저가 요청하려고 시도했던 주소에 “OPTIONS” 메서드를 이용해 요청을 의도적으로 허용하고 있는게 맞는지 확인합니다. 이것을 CORS preflight 라고 부릅니다.
- 요청을 받은 서버는 평소와 똑같이 응답을 합니다.
- 응답을 받은 브라우저는 요청에 대한 허가를 받지 못했다고 판단하고 CORS 에러를 발생시킵니다.
- 이것은 브라우저에 구현된 비동기 요청 API에서만 동작하는 규칙으로, Thunder Client 같은 API Client를 이용해 요청하는 경우에는 절대 발생하지 않습니다.
Origin(출처)
Origin(출처)란
서버의 위치를 의미하는 https://googole.com과 같은 URL들은 마치 하나의 문자열 같이 보여도
사실은 여러 개의 구성 요소로 이루어져 있다.
이때 출처는 protocol, host, port를 모두 합친것을 의미한다.
즉, 서버의 위치를 찾아가기 위해 필요한 가장 기본적인 것들을 합쳐놓은 것이다.
Same-Origin발생 경우
- protocol 불일치 ( http, https)
- host 불일치
http://google.com
http://api/google.com - port 불일치
http://google.com:8000
http://google.com:8080
SOP (Same-Origin Policy)
웹 생태계에는 다른 출처로의 리소스 요청을 제한하는 것과 관련된 두 가지 정책이 존재한다.
한 가지는 CORS, 또 다른 한 가지가 SOP(Same-Origin Policy)이다.
SOP(Same-Origin Policy)란 말 그대로 '같은 출처'에서만 리소스를 서로 공유할 수 있는 정책이다.
그러나 웹 환경에서는 다른 출처에 있는 리소스를 가져와서 사용하는 일은 굉장히 흔한 일이다.
따라서 몇가지 예외 조항을 두고 이 조항에 해당하는 리소스 요청은 출처가 다르더라도 허용하기로 했는데, 그 중 하나가 "CORS 정책을 지킨 리소스 요청"이다.
우리가 다른 출처로 리소스를 요청한다면 SOP 정책을 위반한 것이 되나, SOP의 예외 조항인 CORS 정책을 지킨 리소스 요청이라면 다른 출처의 리소스를 사용할 수 있는 것이다.
CORS 동작 방식
- 브라우저에서 Header 객체의 Origin 속성에 요청을 보내는 출처를 담아 서버에게 요청을 보낸다.
(Origin: https://example.com) - 이후 서버가 이 요청에 대한 응답을 할 때, 응답 Header의 Access-Control-Allow-Origin 에 '리소스를 접근하는 것이 허용된 출처'를 담아 브라우저에게 응답을 보낸다.
- 응답을 받은 브라우저는 자신이 보냈던 요청의 Origin과 서버가 보내준 응답의 Access-Control-Allow-Origin 값을 비교한다.
- 두 개가 동일하다면 유용한 응답이라 판단한다.
기본적인 흐름은 위와 같지만, CORS가 동작하는 방식은 한 가지가 아니라 아래 세 가지 시나리오에 따라 변경된다.
- Simple Request
- Preflight Request
- Credential Request
Simple Request
- 예비요청을 보내지 않고 바로 서버에게 본 요청을 보낸 뒤, 서버가 이에 대한 응답의 헤더에 Access-Control-Allow-Origin과 같은 값을 보내주면 그 때 브라우저가 CORS 정책 위반 여부를 검사하는 방식.
- 프라플라이트와 단순 요청 시나리오는 전반적인 로직 자체는 같으나, 예비 요청의 존재 유무만 다르다.
- simple request이 보내지는 조건 (조건이 까다로워 이 시나리오를 사용하는 경우는 거의 X)
- GET, HEAD, POST 요청
- Accept, Accept-Language, Content-Language, Content-Type, DPR, Downlink, Save-Data, Viewport-Width, Width를 제외한 헤더를 사용하면 안된다.
- 요청 메서드가 POST일 때 Content-Type는 application/x-www-form-urlencoded, multipart/form-data, text/plain만 허용된다.
Preflight Request
- 브라우저의 요청이 안전하고 유효한지를 확인하기 위해 브라우저가 본 요청을 보내기 전에 OPTIONS 메서드로 예비 요청(Preflight)을 보내는 방식.
- preflight request이 보내지는 조건
- GET, HEAD, POST 요청
- Content-Type 헤더가 다음 값들인 경우 : application/x-www-form-urlencoded, multipart/form-data, text/plain
- 요청에 사용된 XMLHttpRequest.upload 객체에 이벤트 리스너가 등록되어 있지 않을 때
- ReadableStream 객체가 요청에서 사용되지 않을 때
- 일반적으로 웹 어플리케이션을 개발할 때 가장 마주치는 시나리오이다.
Flow
- 자바스크립트의 fetch API를 사용하여 브라우저에게 리소스를 받아오도록 요청
- 브라우저는 서버에게 예비 요청을 먼저 보내고, 서버는 이 예비 요청에 대한 응답으로 현재 자신이 어떤 것들을 허용하고, 어떤 것들을 금지하고 있는지에 대한 정보를 응답 헤더에 담아서 브라우저에게 다시 보내주게 된다.
- 브라우저는 자신이 보낸 예비 요청과 서버가 응답에 담아준 허용 정책을 비교한다.
- 이 요청을 보내는 것이 안전하다고 판단되면 같은 엔드포인트로 다시 본 요청을 보내게 된다.
- 이후 서버가 이 본 요청에 대한 응답을 하면 브라우저는 최종적으로 이 응답 데이터를 자바스크립트에게 넘겨준다.
Credential Request
- 인증된 요청을 사용하는 방식
- CORS의 기본적인 방식이라기 보다는 다른 출처 간 통신에서 좀 더 보안을 강화하고 싶을 때 사용한다.
- 기본적으로 브라우저가 제공하는 비동기 리소스 요청 API인 XMLHttpRequest객체나 fetch API는 별도 옵션 없이는 브라우저의 쿠키 정보나 인증과 관련된 헤더를 함부로 요청에 담지 않는다.
인증과 관련된 정보를 요청에 담아 전달하고 싶다면 credentials 옵션을 사용하면 된다.- credentials의 값으로는 same-origin, include, omit 이 있다.include : 모든 요청에 인증 정보를 담을 수 있다.
- ommit : 모든 요청에 인증 정보를 담지 않는다.
- same-origin(기본값) : 같은 출처의 간 요청에만 인증 정보를 담을 수 있다.