티스토리 뷰

웹 개발자라면 한 번쯤은 CORS(Cross - Origin - Resource - Sharing) 정책 이슈를 경험해 봤을 것입니다. 저 또한 대학생 시절에 했던 토이 프로젝트에서 CORS 이슈를 경험했고, 처음 맞닥뜨렸을 때는 무엇이 문제인지 알기가 어려웠던 기억이 있습니다.

 

CORS는  Cross-Origin Resource Sharing의 줄임말로, 교차 출처 리소스 공유라고 해석할 수 있습니다.

교차 출처 리소스 공유... 개인적으로 말이 너무 어렵다고 생각합니다.

'교차 출처(Cross-Origin)' 라고 하는 것은 쉽게 말하면 '다른 출처'라고 생각하면 좀 더 쉽게 이해할 수 있을 것 같습니다.

 

먼저, 출처가 무엇인지 알아보겠습니다.

 

Origin

서버의 위치를 의미하는 URL은 여러 구성 요소로 이루어져 있습니다. 웹 프로토콜을 나타내는 http와 https, Host 주소, Port 번호, path, query string 등 여러 구성 요소가 URL을 이룹니다.

 

여기서 Origin은 Protocol, Host, Port를 합쳐놓은 것을 말합니다. 즉, https://dd-developer.tistory.com:443 이 origin이 된다는 것입니다. 일반적인 URL은 port 번호가 명시되어 있지 않습니다. 왜냐하면 웹에서 사용하는 HTTP, HTTPS 프로토콜의 기본 포트가 정해져 있기 때문입니다.

 

Http에서 port번호가 주어지지 않으면 80번이 자동으로 할당되고, https에서 port 번호가 주어지지 않으면 443번이 자동으로 할당됩니다. 그러나 만약 https://naver.com:443과 같이 출처에 포트 번호가 명시적으로 포함되어 있다면 이 포트 번호까지 모두 일치해야 같은 출처라고 인정된다고 합니다.

F12를 누르고 console에서 console.log(location.origin)을 입력하면 해당 웹 사이트의 origin을 확인할 수 있습니다.

 

이렇게 웹 상에서 origin을 이용해 다른 출처로의 리소스 요청을 제한하는 정책이 2가지가 있습니다.

첫 번째 방법은 SOP, 두 번째 방법은 CORS입니다.


SOP(Same - Origin Policy)

SOP(Same - Origin Policy)는 같은 origin의 리소스 요청만 허락하는 정책입니다. 2011년, RFC 6454에서 처음 등장한 보안 정책으로 말 그대로 “같은 출처에서만 리소스를 공유할 수 있다”라는 규칙을 가지고 있습니다. SOP의 주요 목적은 웹 페이지 간의 정보 유출을 방지하고, 악성 스크립트에 의한 공격을 방어하기 위함입니다. 이를 통해 동일한 출처에서 로드된 리소스 간에만 상호작용이 가능하도록 제한하고, 다른 출처의 리소스 접근을 차단합니다.

 

https://dd-developer.tistory.com/ 와 동일 출처 판단

 

 

여기서 중요한 사실 한 가지는 이렇게 출처를 비교하는 로직이 서버에 구현된 스펙이 아니라 브라우저에 구현되어 있는 스펙이라는 것입니다. 즉 서버 입장에서는 SOP와는 관계없이 요청에 대해 정상적으로 응답하고, 브라우저 단에서 이 응답을 분석해 사용할지 버릴지 결정합니다. 그러므로 Postman이나 curl, 모바일 서버에서 요청을 보내는 경우 sop, cors 정책 이슈가 발생하지 않습니다.

 

시간이 지나가면서, 웹이라는 환경에서 다른 출처에 있는 리소스를 가져와서 사용하는 일은 굉장히 흔한 일이 되었습니다. 그래서 몇 가지 예외 조항을 두고 이 조항에 해당하는 리소스 요청은 출처가 다르더라도 허용하기로 했습니다. 그중 하나가 CORS 정책을 준수하는 리소스 요청 입니다.


CORS

CORS(Cross-Origin Resource Sharing)는 웹 애플리케이션에서 동일 출처 정책(SOP)을 우회하여 다른 출처의 리소스에 접근할 수 있도록 허용하는 메커니즘입니다. SOP는 웹 보안을 위해 동일한 출처에서만 상호작용이 가능하도록 제한하고 있기 때문에, 다른 출처의 리소스에 접근하려면 CORS를 설정해야 합니다.

 

일반적으로, 웹 페이지나 애플리케이션은 동일한 출처에서 로드된 리소스만 접근할 수 있습니다. 그러므로 CORS 헤더를 사용하여 웹 애플리케이션에서의 접근을 허용하도록 설정해야 합니다.

 

CORS는 다음과 같은 절차로 동작합니다.

 

기본적으로 웹 클라이언트 애플리케이션이 다른 출처의 리소스를 요청할 때는 HTTP 프로토콜을 사용하여 요청을 보내게 되는데, 이때 브라우저는 요청 헤더에 Origin이라는 필드에 요청을 보내는 출처를 함께 담아 보낸다.

Origin: https://dd-developer.tistory.com

이후 서버가 요청에 대한 응답을 할 때 응답 헤더의 Access-Control-Allow-Origin이라는 값에 “https://dd-developer.tistory.com”를 설정합니다. 이후 응답을 받은 브라우저는 자신이 보냈던 요청의 Origin과 서버가 보내준 응답의 Access-Control-Allow-Origin을 비교한 후, 이 응답이 유효한 응답인지 아닌지를 결정합니다.

 

 

요약하면

1. 웹 애플리케이션에서 다른 출처의 리소스에 HTTP 요청을 보낸다.

2. 리소스 서버는 요청에 응답할 때, 응답 헤더에 CORS 관련 응답 헤더를 포함한다.

3. 웹 애플리케이션은 응답 헤더를 확인하여, 리소스 접근을 허용할지 결정한다.

4. 웹 애플리케이션이 리소스 접근을 허용하는 경우, 요청에 대한 응답을 처리한다.

 

👉 요청 헤더

 

Origin : 서버로 리소스 요청을 보내는 출처를 지정합니다.

Access-Control-Request-Method : 서버로 리소스 요청을 보낼 메서드를 지정합니다.

Access-Control-Request-Headers : 서버로 리소스 요청을 보낼 때 포함될 헤더를 지정합니다.

 

👉 응답 헤더

 

Access-Control-Allow-Origin: 서버에서 리소스 요청을 허용하는 origin(또는 *)을 지정합니다.

Access-Control-Allow-Methods: 서버에서 리소스 요청을 허용하는 HTTP 메서드를 지정합니다.

Access-Control-Allow-Headers: 서버에서 리소스 요청을 허용하는 요청 헤더를 지정합니다.

Access-Control-Max-Age : 서버에서 Preflight 응답을 캐시 하는 시간을 지정합니다.

Access-Control-Allow-Credentials: 쿠키와 인증 헤더를 허용할지 여부를 지정합니다.

Access-Control-Expose-Headers: 브라우저가 접근 가능한 응답 헤더를 지정합니다.


CORS 유형

Preflight Request

Preflight Request는 일반적으로 웹 어플리케이션을 개발할 때 가장 많이 마주치는 상황입니다. 브라우저는 요청을 한 번에 보내지 않고, 예비 요청과 본 요청으로 나누어 서버로 전송합니다. 이 때 예비 요청을 Preflight라고 부르며, HTTP 메서드 중 OPTIONS 메서드가 사용됩니다. 즉 본 요청을 보내기 전에 전송하기 안전한지 확인하는 요청이고, 헤더에 Access-Control-Request-Method, Access-Control-Request-Headers 가 추가로 전송됩니다.

 

Preflight를 왜 사용하는지 생각해볼 필요가 있습니다. 만약 클라이언트에서 POST나 DELETE 메서드 등을 이용해 서버에 존재하는 리소스들을 삭제하는 요청이 발생하고, 서버에서는 해당 요청을 처리한다면, 서버에는 리소스가 사라지고 클라이언트는 요청을 정상적으로 처리하지 못한 상황이 발생할 수 있습니다.

 

 

Simple Request

Simple Request는 말 그대로 단순한 요청 자체라고 볼 수 있습니다. 단순한 요청이지만 Simple Request 조건은 꽤나 까다롭습니다.

* 메소드가 GET, HEAD, POST 중 하나여야 한다.

* User Agent가 자동으로 설정한 헤더를 제외하면, 아래와 같은 헤더들만 사용할 수 있다.
	* Accept
    * Accept-Language
    * Content-Language
    * Content-Type
    * DPR
    * Downlink (en-US)
    * Save-Data
    * Viewport-Width
    * Width
    
* Content-Type 헤더에는 아래와 같은 값들만 설정할 수 있다.
    * application/x-www-form-urlencode
    * multipart/form-data
    * text/plain

위와 같은 조건을 만족하는 단순 요청은 안전한 요청으로 취급되어, Preflight 요청 필요 없이 단 한 번의 요청만을 전송합니다. 다른 Origin으로 요청을 보낼 때 Origin 헤더에 자신의 Origin을 설정하고, 서버로부터 응답을 받으면 응답의 Access-Control-Allow-Origin 헤더에 설정된 Origin의 목록에 요청의 Origin 헤더 값이 포함되는지 검사합니다.

 

Credentialed Request

Credentials Request는 인증된 요청을 사용하는 방법입니다. 기본적으로 브라우저가 제공하는 비동기 리소스 요청 API인 XMLHttpRequest 객체나 fetchAPI는 별도의 옵션 없이 브라우저의 쿠키 정보나 인증과 관련된 헤더를 요청에 담지 않습니다. 이 때 요청에 인증과 관련된 정보를 담을 수 있게 해주는 옵션이 credentials 옵션입니다.

 

credentials 옵션은 3가지 값을 사용할 수 있습니다.

 

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

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

3. omit: 모든 요청에 인증 정보를 담지 않는다.

 

댓글