CORS 의 동작원리
프론트엔드 개발관련 공부내용을 기록하는 포스트 입니다.
1. 개요
앞선 포스팅에서 CORS
가 무엇이고 왜 개발자가 어떤 부분을 고려해야 하는지에 대해서 정리해보았다.
이제 실제로 CORS
가 어떤 프로세스로 진행되는지를 자세하게 확인해 보도록 한다.
이번 포스팅 또한 evan-moon 님의 블로그를 참조하여 내가 이해한 부분을 정리해 보도록 한다.
2. CORS 는 어떻게 동작할까?
CORS
는 서로 다른 출처(Origin)
를 가진 리소스를 안전하게 사용할 수 있도록 하는 하나의 메커니즘 이라 정의 하였다.
어플리케이션이 클라이언트에서 다른 출처(Origin)의 리소스를 요청할 경우 HTTP
프로토콜을 사용하며, Header
에 Origin
이라는 필드에 요청을 보내는 출처를 함께 담아 보낸다.
위와 같이 Request Header
에 Origin
을 담아 보내게 되면 서버가 응답을 할 때 응답 헤더내의 Access-Control-Allow-Origin
에 이 리소스를 접근하는 것이 허용된 출처를 클라이언트에 전달한다.
이제 응답을 받은 브라우저는 요청 헤더의 Origin
과 응답 Header
의 Origin
과 비교하여 해당 응답이 유효한지를 확인한다.
기본적인 흐름은 위와 같고 CORS
가 동작하는 방식은 세가지 시나리오를 적절히 활용하여 정책을 확인하기 때문에 3가지를 알아본다.
2-1. Prefligiht Request
첫번째 시나리오는 Preflight(프리플라이트)
qkdtlrdlek.
일반적으로 가장 많이 사용하는 방식이며, 브라우저는 요청을 한번에 보내지 않고 예비 요청
과 본 요청
으로 나누어서 서버에 리소스를 요청한다.
이때 본 요청
을 보내기 전 보내는 예비 요청
을 Preflight
라고 부르며, HTTP / OPTION
메소드를 활용하여 요청한다.
Javascript
로직에서 fetch
API를 날리면 브라우저는 서버에 예비 요청
을 먼저 보낸 후 출처의 서버에서 허용한다는 응답이 안전한지가 확인되면, 본 요청
을 다시 보내 원하는 리소스를 응답 받는다.
이후 최종적으로 Javascript
에 Promise
를 넘겨 준다.
이부분에서 응답하는 부분에 Access-Control-Allow-Origin
의 값을 확인해보면 된다.
현재 본인의 로컬에 간단한 index.js
를 만들어서 본인 블로그에 fetch
를 보내면 어떻게 될까??
// index.js
const headers = new Headers({
"Content-Type": "text/xml",
});
fetch("https://jjou33.github.io", { headers });
위 소스를 통해 보내면 이런 통신 에러를 확인할 수 있다.
실제 요청하는 출처(Origin)는 https://127.0.0.1:5502
포트는 vscode 의 GoLive
익스텐션을 사용하기 때문이며, 아마 github
서버에서 보낸 Origin
은 https://jjou33.github.io
로 보낼 것이다.
브라우저는 내 이 두개의 출처(Origin)이 다르기 때문에 CORS
에러를 발생하였고, preflight
에러가 확인된다.
2-2. Simple Request
Preflight
시나리오에서 예비 요청
을 제외 시킨걸 Simple Request
라고 MDN CORS
문서에서 명명하고 있다.
이 시나리오는 예비 요청
없이 바로 서버에서 본 요청
을 전달하고, 응답의 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
만 허용된다.
이러한 조건이 있기 때문에 설계시 고려해야 할 사항들이 많아 사용하기 어렵다.
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
정책 위반 여부를 검사하는 두가지 룰이 추가되기 떄문이다.
-
Access-Control-Allow-Origin
에는*
를 사용할 수 없으며, 명시적인 URL이어야한다. -
응답 헤더
에는 반드시Access-Control-Allow-Credentials: true
가 존재해야한다.
이렇게 두가지 롤이 충족되야 하지만, 위 요청에서는 1,2 번모두 충족되지 않아 CORS
위반 에러가 발생한 것으로 보인다.