CORS란?
들어가기전
등장 배경
예전에는 클라이언트가 동일한 서버에서만 리소스를 받았기 때문에, 리소스의 위험성에 대한 의심의 여지가 없었다. 그런데, 계속해서 웹 생태계가 다양화되면서 서로 다른 도메인 간에 데이터를 주고 받을 일이 많아졌다.
브라우저는 기본적으로 안정성을 이유로 동일한 출처의 리소스만 허용하는데 (SOP), 개발자들은 이를 우회하기 위해 JSONP를 사용하는 등 꼼수를 사용했다. 이런 배경에서 다른 장치의 도입이 필요해졌고, 합의된 출처 간에 안전한 리소스 공유를 위해 만들어진 메커니즘이 CORS이다.
JSONP : HTML 문서의 script 태그로 다른 도메인을 요청 할 시 SOP 정책이 적용되지 않는다는 점을 이용해 다른 도메인으로부터 데이터를 받는 요청 방식이다. (더 이상 사용하지 않는다.)
교차 출처의 위험성
교차 출처 리소스 공유(CORS)를 알기 전에 왜 브라우저가 기본적으로 동일 출처 리소스 공유(SOP)를 원칙으로 하는지 알아야한다. 브라우저는 사용자의 민감한 정보를 지니고 있기 때문에, 안전하게 보호되어야 한다.
민감한 정보라고 하면 대표적으로 토큰이 있다. 토큰은 원격 서버에 접근 할 수 있는 인증 정보다. 만약, 브라우저에서 CORS가 비활성화 되어있다면 다음과 같은 최악의 시나리오를 생각해볼 수 있다.
내 브라우저는 사이트 A에 로그인을 하여 토큰을 가지고 있다. 그리고 한 해커가 사이트 A를 사칭하여 메일을 보내 사이트 B의 페이지의 링크를 보냈고 나는 그 링크를 클릭했다. 이 페이지를 방문하면 사이트 A의 페이지 콘텐츠와 인증 정보를 반환하는 요청을 하게되고 해커는 내 사이트 A 계정으로 사이트 A를 접속할 수 있게 된다.
CORS
CORS는 HTTP 헤더에 CORS 관련 항목을 추가하여, 웹 어플리케이션이 출처가 다른 리소스에 접근이 가능하도록 브라우저에게 알려주는 체제이다.
CORS에서 가장 헷갈리는 부분들을 먼저 짚고 넘어가자.
- CORS는 방어가 아닌 방어를 풀어주는 역할을 한다. 교차 출처 리소스 접근을 막는 것은 SOP이고 CORS는 이를 허용해준다.
- SOP는 서버가 아닌 브라우저의 방어 수단이다. 즉, 브라우저가 신뢰할 수 없는 출처의 리소스를 차단하는 것이다.
출처란?
브라우저는 다음 3가지를 기준으로 출처가 같은지 아니면 다른지 판단한다.
1. 프로토콜
2. 호스트
3. 포트 (보통 생략, 명시될 경우 일치해야 함)
이 3가지가 모두 같으면 동일 출처(Same-Origin) 요청이고, 하나라도 다르면 교차 출처(Cross-origin) 요청이다. 교차 출처 요청을 할 경우, CORS 정책을 준수해야 정상적으로 응답을 받을 수 있다.
CORS의 동작
CORS는 기본적으로 다음과 같은 과정으로 동작한다.
- 클라이언트가 HTTP 헤더에
Origin
필드의 값으로 출처(현재 접속한 사이트)를 담아 요청을 보낸다. - 서버가 헤더에
Access-Control-Allow-Origin
필드의 값으로 허용된 출처 (신뢰할 수 있는 사이트나 API 주소 목록)를 담아 응답을 보낸다. - 클라이언트의 브라우저는
Origin
값과Access-Control-Allow-Origin
값을 비교해 유효한 응답인지 확인한다. 유효하지 않으면 응답을 사용하지 않는다.
CORS 요청 방식 3가지
Simple Request
아래와 같은 일정 조건들을 모두 충족한 요청에 사용한다. 이 까다로운 조건들 중 하나라도 만족하지 못할 시, 더 안전한 요청 방식인 Preflight Request를 보낸다.
- 조건 1 - 메서드
GET
,POST
,HEAD
중 하나- 조건 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
중 하나
Preflight Request
본 요청을 보내기 전에 Preflight Request(예비 요청)을 먼저 보낸다. 본 요청이 서버 데이터에 영향을 줄 수 있기 때문에, 먼저 안전한지 확인하는 것이다. 예비 요청에서 허락이 떨어지면, 본 요청이 가능하다.
예비 요청은 OPTIONS
메서드를 사용해야 하고, 헤더의 Origin
필드의 값으로 출처에 대한 정보가 들어가야 한다. 이 때 헤더에는 Origin
뿐만 아닌, 본 요청에 대한 다른 정보도 포함될 수 있다.
본 요청의 응답이 돌아오면, 브라우저는 응답이 CORS 정책을 준수하고 있는지 심사한다. 브라우저는 일단 응답을 받기 때문에, CORS를 위반해도 응답 헤더에 200 - OK
가 뜬다. 따라서, 정책 위반 판단 여부는 상태코드가 아닌 Access-Control-Allow-Origin
값이 존재하는지 여부로 판단해야 한다.
매 요청마다 예비 요청을 보내는 것은 비효율적이다. 한 가지 옵션으로 백엔드는 예비 요청에 대한 응답에
Access-Control-Max-Age
(CORS가 캐싱되는 시간)를 포함하여 프론트에게 준다. 그러면, 해당 시간만큼 프론트는 예비 요청을 프리 패스할 수 있다.
Credentialed Request
앞서 말했듯이, 토큰 등 사용자 식별 정보는 악용될 수 있으므로 이를 담은 요청에 대해선 보다 엄격한 조건이 요구된다. 이 조건을 갖춘 요청이 Credentialed Request다.
브라우저가 기본 제공하는 비동기 리소스 요청들 (XMLHttpRequest
, fetchAPI
)은 옵션을 따로 주지 않으면 헤더에 쿠키나 인증 정보를 함부로 담지 않는다. 만약, 요청에 이를 담고 싶다면 credential
옵션에 값을 주어야 한다.
이 옵션에는 3가지 값을 사용할 수 있다.
- same-origin : 동일 출처 간 요청에만 인증 정보를 담을 수 있다. (기본 값)
- include : 모든 요청에 인증 정보를 담는다.
- omit : 모든 요청에 인증 정보를 담지 않는다.
이 중 include 값을 사용하기 위해선 2가지 조건을 더 충족해야 한다.
Access-Control-Allow-Origin
의 값이 와일드 카드 (* : 모두 허용)이 아닌 출처 주소를 정확하게 명시해야 한다.- 응답 헤더에 반드시
Access-Control-Allow-Credentials : true
가 있어야 한다.
CORS 해결 방법
CORS 정책을 위반한 경우, 보통 Access-Control-Allow-Origin
의 값을 올바르게 주면 해결된다. 와일드 카드(*)를 값으로 지정해주면, 모든 출처를 허용하기 때문에 약간 위험할 수 있다. 따라서 정확한 출처 주소를 명시해주는 것이 더 안전하게 해결하는 방법이다.
요약
- CORS는 교차 출처 리소스를 허용해주는 체제고 SOP(브라우저 기본 값)는 이를 차단하는 체제다.
- CORS 요청 방식은 Simple Request, Preflight Request, Crendential Request가 있다. 열거 순으로 요청 조건이 더 까다롭지만 그만큼 더 안전하다.
- 보통
Access-Control-Allow-Origin
필드의 값을 올바르게 주면 CORS 에러를 해결할 수 있다.
Leave a comment