개발자 올라프 2022. 8. 16. 06:16

CORS란?

 

교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처(프로토콜, 도메인, 포트번호)의 리소스에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제이다. 웹 애플리케이션은 리소스가 자신의 출처와 다를 때 교차 출처 HTTP 요청을 실행한다.

 

쉽게 말해서 도메인, 프로토콜, 포트 번호가 하나라도 다를 경우에 출처가 다른 교차 출처(Cross-Origin)라고 판단되며 브라우저에서는 보안 때문에 Cross-Origin HTTP 요청을 제한한다. 권한을 부여 받기 위한 Cross-Origin 요청은 서버에서 허가를 받아야 하는데, HTTP-header를 통해서 받을 수 있다.

 

출처를 비교하는 방법은 URL 구성요소 중 프로토콜, 호스트, 포트 세가지만 동일한지 확인하면 된다. 같은 프로토콜, 호스트, 포트를 사용한다면 같은 출처로 인정되며, 리소스가 자신의 출처와 다를경우 브라우저는 교차출처 요청을 실행한다. 한마디로 정리하면 프로토콜, 포트, 호스트가 모두 일치하면 Same Origin이며, 하나라도 일치하지 않는다면 Cross-Origin이 된다.

 


 

CORS가 필요한 이유

 

출처가 다른 두 개의 어플리케이션이 마음대로 소통하는 환경은 위험한 환경이다. 웹에서 돌아가는 클라이언트 애플리케이션은 사용자 공격에 매우 취약하다. 개발자 도구만 열더라도 DOM, JavaScript코드 등 각종 통신 정보를 쉽게 열람할 수 있다. 이를 통해서 사용자가 악의를 가지고 다른 사이트로 본 사이트를 모방할 수 있다. 이렇게 다른 출처의 어플리케이션이 통신하는 것에 제약이 없다면, 기존 사이트와 동일하게 동작하여 사용자의 정보가 탈취되기 쉬워진다. 

 


 

Same-Origin-Policy(동일 출처 정책)란?

 

웹에는 크게 SOP(Same Origin Policy)와 CORS(Cross-Origin Resource Sharing) 두 가지 정책이 있다. SOP는 같은 출처에서만 리소스를 공유할 수 있다는 규칙을 가진 정책이다.

 

Postman과 같은 다른 서버에서 API를 호출할 때는 잘 동작하다가 브라우저에서 API를 호출할 때만 CORS Policy 오류가 발생한 적 있을 것이다. 이유는 브라우저가 동일 출처 정책을 지키고 있기 때문에 다른 출처의 리소스 접근을 금지하고 있기 때문이다. 다만 브라우저에서만 오류가 발생한 이유는 CORS가 브라우저의 구현 스펙에 포함되는 정책이기 때문에 브라우저를 통하지 않고 서버 간 통신을 할 때는 적용되지 않기 때문이다.

 


 

CORS가 동작하는 원리

 

웹 애플리케이션이 다른 출처의 리소스에 접근할 때는 HTTP header에 요청을 보낸다고 하였다. HTTP 프로토콜을 사용하여 요청을 보내며, 요청 헤더에 Origin 필드에 요청을 보내는 출처를 담아서 보낸다.

Origin: https://google.com

이후 서버가 이 요청에 대한 응답을 할 때 응답 헤더의 Access-Control-Allow-Origin이라는 값에 "리소스 접근이 허용된 출처"를 내려주고, 응답을 받은 브라우저는 자신이 보낸 Origin과 서버가 보내준 응답인 Access-Control-Allow-Origin을 비교하고 유효한 응답인지 확인한다. 만약 유효하지 않으면 그 응답을 사용하지 않는다.

 

위 기본적인 흐름 외에도 CORS는 세 가지 시나리오에 따라 변경된다.

 


 

Preflight Request 

 

일반적으로 웹 어플리케이션을 개발할 때 가장 많이 마주치는 시나리오로 이 상황에서 브라우저는 요청을 한 번에 보내지 않고 예비 요청과 본 요청으로 나누어서 서버로 전송한다.

 

본 요청을 보내기 전에 보내는 예비 요청을 Preflight라고 하며, 예비 요청에는 OPTIONS 메소드가 사용된다.

 

 

fetch API를 사용하여 브라우저에게 리소스를 받아오는 명령을 내리면 브라우저는 서버에게 예비 요청을 먼저 보낸다. 서버는 예비 요청 응답으로 현재 어떤 것을 허용하고, 어떤 것을 금지하고 있는지에 대한 정보를 응답 헤더에 담아서 브라우저에게 다시 보내준다.

 

이후 브라우저는 자신이 보낸 예비 요청과 서버가 응답에 담아준 내용인 정책을 비교하여 안전하다고 판단되면 같은 엔드포인트로 다시 본 요청을 보내게 된다. 그리고 서버가 본 요청에 대한 응답을 하면 브라우저는 최종적으로 응답 데이터를 자바스크립트에 넘겨준다.

 


 

Simple Request

 

단순 요청은 예비 요청을 보내지 않고 서버에 본 요청을 보낸 후, 서버가 응답 헤더에 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만 허용된다.

 


 

Credentialed Request

 

인증된 요청을 사용하는 방법으로 CORS 기본적인 방식 보다는 다른 출처 간 통신에서 조금 더 보안을 강화하고 싶을 때 사용하는 방법이다.

 

XMLHttpRequest객체나 fetch API는 별도의 옵션 없이 브라우저의 쿠키 정보나 인증과 관련된 헤더를 요청에 담지 않는다. 이때 요청에 인증 관련 정보를 담을 수 있게 해주는 옵션이 바로 credentials 옵션이다.

 

이 옵션에는 3가지의 값을 사용할 수 있다.

옵션 값 설명
same-origin (default) 같은 출처 간 요청에만 인증 정보를 담을 수 있다
include 모든 요청에 인증 정보를 담을 수 있다
omit 모든 요청에 인증 정보를 담지 않는다

 

만약 same-origin이나 include와 같은 옵션을 사용하여 리소스 요청에 인증 정보가 포함되면, 브라우저는 Cross-Origin 리소스를 요청할 때 단순히 Access-Control-Allow-Origin만 확인하는 것이 아니라 조금 더 빡빡한 검사 조건을 추가하게 된다.

 


 

CORS 해결하기

 

Access-Control-Allow-Origin 세팅

 

가장 대표적인 방법은 서버에서 Access-Control-Allow-Origin 헤더에 알맞은 값을 세팅하는 것이다.

Access-Control-Allow-Origin: https://google.com