교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처(프로토콜, 도메인, 포트번호)의 리소스에 접근할 수 있는
권한을 부여하도록 브라우저에 알려주는 체제이다.
웹 애플리케이션은 리소스가 자신의 출처와 다를 때 교차 출처 HTTP 요청을 실행한다.
쉽게 말해서 도메인, 프로토콜, 포트 번호가 하나라도 다를 경우 출처가 다른 교차 출처(Cross-Origin)라고
판단되며, 브라우저에서는 보안 때문에 Cross-Origin HTTP 요청을 제한한다.
권한을 부여받기 위한 Cross-Origin 요청은 서버에서 허가를 받아야 하는데, HTTP-header를 통해서 받을 수 있다.
출처를 비교하는 방법 :
URL 구성요소 중 프로토콜, 호스트, 포트 번호 세 가지만 동일한지 확인하면 된다.
같은 프로토콜, 호스트, 포트를 사용한다면 같은 출처로 인정되며,
리소스가 자신의 출처와 다를 경우 브라우저는 교차 출처 요청을 실행한다.
한 마디로 정리하면
프로토콜, 호스트, 포트가 모두 일치하면 Same Origin이며, 하나라도 일치하지 않는다면 Cross-Origin이 된다.
출처가 다른 2개의 애플리케이션이 마음대로 소통하는 환경은 위험한 환경이다.
웹에서 돌아가는 클라이언트 애플리케이션은 사용자 공격에 매우 취약하다. 개발자 도구만 열더라도 DOM, JavaScript 코드 등 각종 통신 정보를 쉽게 열람할 수 있다.
이를 통해서 사용자가 악의를 가지고 다른 사이트로 본 사이트를 모방할 수 있다.
이렇게 다른 출처의 애플리케이션이 통신하는 것에 제약이 없다면, 기존 사이트와 동일하게 동작하여 사용자의 정보가 탈취되기 쉬워진다.
웹에는 크게 SOP(Same Origin Policy)와 CORS(Cross-Origin Resource Sharing) 두 가지 정책이 있다.
SOP는 같은 출처에서만 리소스를 공유할 수 있다는 규칙을 가진 정책이다.
예를 들어, Postman과 같은 다른 서버에서 API를 호출할 때는 잘 동작하다가 브라우저에서 API를 호출할 때만 CORS Policy 오류가 발생하는 경우가 있다.
이유는 브라우저가 동일 출처 정책을 지키고 있기 때문에, 다른 출처의 리소스 접근을 금지하고 있기 때문이다.
다만 브라우저에서만 오류가 발생한 이유는 CORS가 브라우저의 구현 스펙에 포함되는 정책이기 때문에
브라우저를 통하지 않고 서버 간 통신을 할 때는 적용되지 않기 때문이다.
웹 애플리케이션이 다른 출처의 리소스에 접근할 때는 HTTP header에 요청을 보낸다.
HTTP 프로토콜을 사용하여 요청을 보내며,
요청 헤더에 Origin
필드에 요청을 보내는 출처를 담아서 보낸다.
Origin: https://google.com
이후 서버가 이 요청에 대한 응답을 할 때,
응답 헤더의 Access-Control-Allow-Origin
이라는 값에 “리소스 접근이 허용된 출처”를 내려주고,
응답을 받은 브라우저는 자신이 보낸 Origin
과 서버가 보내준 응답인 Access-Control-Allow-Origin
을 비교하고 유효한 응답인지 확인한다. 만약 유효하지 않으면 그 응답을 사용하지 않는다.
위의 기본적인 흐름 외에도 CORS는 3가지 시나리오에 따라 변경된다.
일반적으로 웹 애플리케이션을 개발할 때 가장 많이 마주치는 시나리오로,
이 상황에서 브라우저는 요청을 한 번에 보내지 않고 예비 요청과 본 요청으로 나누어 서버로 전송한다.
본 요청을 보내기 전에 보내는 예비 요청을 Preflight라고 하며, 예비 요청에는 OPTIONS 메소드가 사용된다.
fetch API를 사용하여 브라우저에게 리소스를 받아오는 명령을 내리면,
브라우저는 서버에게 예비 요청을 먼저 보낸다.
서버는 예비 요청에 대한 응답으로 어떤 것을 허용하고, 어떤 것을 금지하고 있는지에 대한 정보를 응답 헤더에 담아서 브라우저에게 다시 보내준다.
이후 브라우저는 자신이 보낸 예비 요청과 서버가 응답에 담아준 내용인 정책을 비교하여
안전하다고 판단되면 같은 엔드포인트로 다시 본 요청을 보내게 된다.
그리고 서버가 본 요청에 대한 응답을 하면 브라우저는 최종적으로 응답 데이터를 자바스크립트에 넘겨준다.
단순 요청은 예비 요청을 보내지 않고 서버에 본 요청을 보낸 후,
서버가 응답 헤더에 Access-Control-Allow-Origin
과 같은 값을 보내면, 그 때 브라우저가 CORS 정책 위반 여부를 검사하는 방식이다.
예비 요청을 생략할 수 있는 Simple Request는 특정 조건을 만족해야 한다.
요청 메소드는 GET
, HEAD
, POST
중 하나여야 한다.
Accept
, Accept-Language
, Content-Language
, Content-Type
, DPR
, Downlink
, Save-Data
, Viewport-Width
, Width
를 제외한 헤더를 사용하면 안 된다.
만약 Content-Type
를 사용하는 경우
application/x-www-form-urlencoded
, multipart/form-data
, text/plain
만 허용된다.
인증된 요청을 사용하는 방법으로, CORS의 기본적인 방식보다는 다른 출처 간 통신에서 조금 더 보안을 강화하고 싶을 때 사용하는 방법이다.
XMLHttpRequest
객체나 fetch API
는 별도의 옵션 없이 브라우저의 쿠키 정보나 인증과 관련된 헤더를 요청에 담지 않는다.
이때 요청에 인증 관련 정보를 담을 수 있게 해주는 옵션이 바로 credentials
옵션이다.
이 옵션에는 3가지 값을 사용할 수 있다.
옵션 값 | 설명 |
---|---|
same-origin (default) | 같은 출처 간 요청에만 인증 정보를 담을 수 있다. |
include | 모든 요청에 인증 정보를 담을 수 있다. |
omit | 모든 요청에 인증 정보를 담지 않는다. |
만약 same-origin
이나 include
와 같은 옵션을 사용하여 리소스 요청에 인증 정보가 포함되면,
브라우저는 Cross-Origin 리소스를 요청할 때 단순히 Access-Control-Allow-Origin
만 확인하는 것이 아니라 조금 더 까다로운 검사 조건을 추가하게 된다.
Access-Control-Allow-Origin 세팅
가장 대표적인 방법은 서버에서 Access-Control-Allow-Origin
헤더에 알맞는 값을 세팅하는 것이다.
Access-Control-Allow-Origin: https://google.com