CORS 의 동작원리


:raising_hand: 프론트엔드 개발관련 공부내용을 기록하는 포스트 입니다.


image

1. 개요


앞선 포스팅에서 CORS가 무엇이고 왜 개발자가 어떤 부분을 고려해야 하는지에 대해서 정리해보았다.

이제 실제로 CORS가 어떤 프로세스로 진행되는지를 자세하게 확인해 보도록 한다.

이번 포스팅 또한 evan-moon 님의 블로그를 참조하여 내가 이해한 부분을 정리해 보도록 한다.

2. CORS 는 어떻게 동작할까?


CORS는 서로 다른 출처(Origin)를 가진 리소스를 안전하게 사용할 수 있도록 하는 하나의 메커니즘 이라 정의 하였다.

어플리케이션이 클라이언트에서 다른 출처(Origin)의 리소스를 요청할 경우 HTTP 프로토콜을 사용하며, HeaderOrigin이라는 필드에 요청을 보내는 출처를 함께 담아 보낸다.

image

위와 같이 Request HeaderOrigin을 담아 보내게 되면 서버가 응답을 할 때 응답 헤더내의 Access-Control-Allow-Origin에 이 리소스를 접근하는 것이 허용된 출처를 클라이언트에 전달한다.

이제 응답을 받은 브라우저는 요청 헤더의 Origin 과 응답 HeaderOrigin과 비교하여 해당 응답이 유효한지를 확인한다.

기본적인 흐름은 위와 같고 CORS가 동작하는 방식은 세가지 시나리오를 적절히 활용하여 정책을 확인하기 때문에 3가지를 알아본다.

2-1. Prefligiht Request

첫번째 시나리오는 Preflight(프리플라이트) qkdtlrdlek.

일반적으로 가장 많이 사용하는 방식이며, 브라우저는 요청을 한번에 보내지 않고 예비 요청본 요청으로 나누어서 서버에 리소스를 요청한다.

이때 본 요청을 보내기 전 보내는 예비 요청Preflight라고 부르며, HTTP / OPTION 메소드를 활용하여 요청한다.

image

Javascript 로직에서 fetch API를 날리면 브라우저는 서버에 예비 요청을 먼저 보낸 후 출처의 서버에서 허용한다는 응답이 안전한지가 확인되면, 본 요청을 다시 보내 원하는 리소스를 응답 받는다.

이후 최종적으로 JavascriptPromise를 넘겨 준다.

이부분에서 응답하는 부분에 Access-Control-Allow-Origin 의 값을 확인해보면 된다.

현재 본인의 로컬에 간단한 index.js를 만들어서 본인 블로그에 fetch를 보내면 어떻게 될까??

// index.js
const headers = new Headers({
  "Content-Type": "text/xml",
});

fetch("https://jjou33.github.io", { headers });

위 소스를 통해 보내면 이런 통신 에러를 확인할 수 있다.

image

실제 요청하는 출처(Origin)는 https://127.0.0.1:5502 포트는 vscode 의 GoLive 익스텐션을 사용하기 때문이며, 아마 github 서버에서 보낸 Originhttps://jjou33.github.io 로 보낼 것이다.

image

브라우저는 내 이 두개의 출처(Origin)이 다르기 때문에 CORS 에러를 발생하였고, preflight 에러가 확인된다.

2-2. Simple Request

Preflight 시나리오에서 예비 요청을 제외 시킨걸 Simple Request 라고 MDN CORS문서에서 명명하고 있다.

이 시나리오는 예비 요청없이 바로 서버에서 본 요청을 전달하고, 응답의 Access-Control-Allow-Origin 값을 브라우저에서 확인하고 CORS 정책 위반 여부를 확인한다.

단, Simple Request는 보낼 수 있는 조건 즉, 예비 요청을 제외시키기 위해서는 몇가지 조건이 있어서 이부분을 확인해야 한다.

  1. 요청의 메소드는 GET, HEAD, POST 중 하나여야한다.
  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만 허용된다.

이러한 조건이 있기 때문에 설계시 고려해야 할 사항들이 많아 사용하기 어렵다.

2-3. Credentialed Request

이 시나리오는 인증된 요청을 사용하며, 보안이 강화된 방법이다.

기본적으로 비동기로 리소스를 요청할때 사용하는 XMLHttpReqeust 객체, fetch API 는 별도의 옵션 없이 브라우저의 쿠키 정보나 인증과 관련된 헤더를 함부로 요청에 담지 않는다.

이때 요청에 인증과 관련된 정보를 담게 해주려면 옵션으로 credentials를 넣어준다.

이 옵션은 3가지가 있다.

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

만약 옵션으로 same-origin 혹은 include가 포함되어 있다면, 프라우저는 다른 출처의 리소스를 요청할 때 단순히 Access-Control-Allow-Origin의 정보만 확인하지 않고 다른 조건도 검사한다.

본인의 블로그에 위와 동일하게 fetch를 통해 리소스를 받아와 보자

const headers = new Headers({
  "Content-Type": "text/xml",
});

fetch("https://jjou33.github.io/feed.xml");

github 리턴값을 보면 구글의 크롬 브라우저의 credential 기본 값은 same-origin 이기 때문에 , Access-Control-Allow-Origin 값이 *로 되어 설정 되어 있어서 CORS 정책 위반에 대한 제약을 받지 않는다.

그럼 이번에 요청 파라미터에 include 옵션을 넣으면 어떻게 되는지 확인해보자.

const headers = new Headers({
  "Content-Type": "text/xml",
});
fetch("https://jjou33.github.io/feed.xml", {
  credentials: "include",
});

이렇게 옵션을 설정하면 Access-Control-Allow-Origin 동일 출처 여부와 상관없이 무조건 요청에 인증 정보가 포함되도록 설정된다.

이때는, 서버에서 응답한 내용은 같지만 CORS 에러가 발생하는것을 볼 수 있으며, include일 경우, Access-Control-Allow-Origin 의 값을 *로 설정하면 안된다고 나온다.

이유는, include일 경우에는 CORS정책 위반 여부를 검사하는 두가지 룰이 추가되기 떄문이다.


  1. Access-Control-Allow-Origin에는 *를 사용할 수 없으며, 명시적인 URL이어야한다.
  2. 응답 헤더에는 반드시 Access-Control-Allow-Credentials: true가 존재해야한다.

이렇게 두가지 롤이 충족되야 하지만, 위 요청에서는 1,2 번모두 충족되지 않아 CORS위반 에러가 발생한 것으로 보인다.

참조 사이트