프론트엔드 개발을 해본 사람이라면 API 요청을 하다가 한번 쯤은 아래와 같은 CORS 에러를 경험해보신 적이 있을 것입니다.

그런데 같은 호출을 Postman으로 호출하면 아주 잘 호출되는 모습을 볼 수 있기도 합니다.
개발하는데 머리를 아프게 하는 CORS 에러가 왜 발생하는지, 그리고 어떤 친구인지 한번 알아보도록 합시다.
웹 브라우저에서 사용하는 JavaScript는 무궁무진한 작업 수행 능력을 가지고 있습니다. JavaScript를 통해 DOM 조작은 물론이고, Cookie를 다룰 수 있고, AJAX 를 통해 여러가지 정보를 외부에서 받아올 수 있습니다.
이러한 특히 AJAX나 Fetch API 를 통한 외부와의 통신을 통해 누구든지 나쁜마음을 먹으면 언제든지 개인의 정보를 탈취할 수 있는 큰 취약점이 발생하게 됩니다.
그래서 브라우저에서는 기본적으로 SOP(Same-Origin-Policy) 라는 정책을 도입하고 있습니다.
앞서 이야기한 JavaScript의 능력으로 인해 CSRF(Cross-Site Request Forgery) 라는 공격을 받을 수 있습니다.
> CSRF(Cross-Site Request Forgery)란?
사용자가 자신의 의지와는 무관하게 공격자가 의도한 행위(데이터 수정, 삭제, 등록 등) 을 특정 웹사이트에 요청하게 하는 공격

CSRF 공격은 보통 스팸 메일 등으로 접근하여, 공격자의 사이트에 접근하게 하여 사용자의 브라우저에 개인정보가 담긴 Cookie에 접근하고, 탈취하는 자바스크립트 파일을 실행하도록 합니다.
이러한 공격은 의도치 않은 사이트 접속으로 인해 사용자의 브라우저에서 외부의 서버에 요청을 보내는 코드가 담긴 자바스크립트 파일이 실행되었기 때문에 일어날 것입니다.
그래서,
브라우저에서는 같은 출처(Origin)에서만 받아오는 요청을 허용하는 동일 출처 정책(SOP)을 제공합니다.
다시말해, 다른 출처(Origin)에서 데이터 접근하는 것을 모두 차단하는 것이 브라우저의 기본 정책입니다.
출처(Origin) 란? -> 출처 Origin(MDN)
인터넷이 발달하게 되면면서 웹 생태계가 다양해졌고, 인터넷 상에 있는 여러 서비스들끼리 서로 데이터를 주고받아질 필요가 생기게 되었습니다.
API를 통해서 외부(다른 사이트)에서 데이터를 이용하는 것이 당연해지고 있는데, 브라우저에서는 SOP 정책을 통해 서로 다른 사이트간의 요청들을 주고 받는 것을 브라우저가 막게 되면서 자유롭지 못한 문제가 발생게 됩니다.
그래서 “서로 합의된 출처(Origin)간에 합법적으로 허용해주기 위해” 새롭게 등장한 것이 *CORS(Cross-Origin Resource Sharing) 입니다.*
"CORS 규칙을 지키면, '다른 출처'에서도 데이터를 불러올 수 있게 해줄게!"
우리가 종종 마주치는 이 “교차 출처 리소스 공유(CORS)”는 다른 출처(Origin)간에 데이터를 주고받는 것을 막는 것이 아니라, 조건부로 허용해주기 위한 정책! 사용자를 위한 것이라는 것이 중요한 뽀인트 입니다!

다른 출처의 데이터(리소스)에 접근하려면, 해당 출처(Origin)에서 올바른 CORS 헤더를 포함한 응답(Response)를 반환해야 합니다.
이러한 CORS 에 대한 조건은 웹 서버에서 반환해주게 됩니다.
그래서 백엔드 서버를 개발하는 Spring, Django, Express 등의 프레임워크의 문서에는 CORS 옵션을 넣을 수 방법이 아주 잘 마련되어 있답니다~!
CORS가 동작하는 방법에는 크게 ”Simple Request”와 ”preflight Request”, 그리고 ”Credentialed Request” 이렇게 세가지 가 있습니다.
브라우저는 서버의 데이터에 큰 영향을 주지 않는 특정 요청에 대해서는 CORS 정책 검사를 진행하지 않습니다.
어떠한 조건을 모두 만족하는 요청을 “Simple Request” 라고 합니다. 브라우저는 다음 조건을 만족하면, 해당 CORS 요청을 알아서 “Simple Request” 으로 처리합니다.
GET, HEAD, POST 중 하나여야 한다.AcceptAccept-LanguageContent-LanguageContent-Type, DPR, Downlink, Save-Data, Viewport-Width, WidthContent-Type를 사용하는 경우에는 아래 내용만 허용application/x-www-form-urlencodedmultipart/form-datatext/plain“Simple Request” 는 다음과 같은 순서로 동작합니다.
Accecss-Control-Allow-Origin 를 추가해 사용자에게 다시 응답을 보내준다.간단한 예제를 살펴보면 아래와 같이 Origin에 자신의 Origin을 담아 요청을 보내면, 웹서버는 확인 후 다음과 같이 응답을 보내주게 됩니다.
// Simple Request 예시
GET /doc HTTP/1.1
Origin: foo.example
// Simple Request 응답 예시
HTTP/1.1 200 OK
Accecss-Control-Allow-Origin : *

“preflight Request”는 서버의 데이터에 영향을 줄 수 있는 요청들이기 때문에 조금 엄격한 순서로 요청을 보내게 됩니다.
앞서 나온 Simple Requests와 다르게 먼저 요청하려는 출처에 HTTP 요청을 보내 실제 요청을 보내기에 안전한지 먼저 확인합니다.
“너 한테 이 요청 보내도 괜찮아??”
요청하는 순서는 다음과 같습니다.
브라우저는 OPIONS 메서드와 몇가지 요청 사항을 통해 다른 출처에 HTTP 요청을 보내 안전한지 확인한다. (preflight Request)
웹 서버는 요청 header 에 담아 날라온 preflight Request 정보를 확인하고, 웹 서버는 자신이 허락(Allow)하는 내용을 응답 header에 담아 다시 브라우저에 보내준다.
브라우저는 웹 서버가 보내준 응답 header에 내용을 확인하고 요청이 가능하다면, 실제 요청을 보내게 된다.
간단한 예시를 통해 봅시다.
처음 preflight Request 를 웹 서버에 보낼 때, 아래 내용을 넣어 보내줘야 합니다.
Origin : 요청 출처
Access-Control-Request-Method : 실제 요청의 메서드
Access-Control-Request-Headers : 실제 요청의 추가 header
// preflight Request 간단 예제
OPTIONS /resources/post-here/ HTTP/1.1
Origin: [https://foo.example](https://foo.example/)
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
이렇게 보내면 웹 서버는 미리 지정해 놓은 내용을 비교하여, 응답을 브라우저에게 보내주게 됩니다. 웹 서버가 브라우저에 다시 보내주는 응답(Response) header에는 다음과 같은 내용들이 포함 됩니다.
Access-Control-Allow-Origin : 서버에서 허가하는 Origin
Access-Control-Allow-Method : 서버에서 허가하는 Method
Access-Control-Allow-Headers : 서버에서 허가하는 Header
Access-Control-Allow-Max-Age : Preflight 응답 캐시 기간
→ 본 요청을 보낼 수 있는 preflight 요청을 유지하는 기간
// preflight Request에 대한 응답 Header 예시
HTTP/1.1 200 No Content
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
이렇게 돌아온 응답 Header를 보고 브라우저는 조건에 맞게 드디어 본 요청을 보내줄 수 있게 됩니다.

“인증 관련 header”를 포함할 때 사용하는 요청이다.
인증관련된 내용이라고 하면, Session-id 와 같은 정보를 담은 Cookie나 JWT 같은 인증과 관련된 내용을 보내고 싶을때, 사용하는 요청입니다.
특별히 보안에 더 엄격해야 하는 요청이다보니 브라우저에서는 기본적으로 제공하는 통신에서 함부로 요청에 해당 내용들을 담으면 안됩니다.
이때 인증과 관련된 정보를 담을 수 있게 해주는 옵션이 바로 Credentials 옵션입니다.
요청에 인증을 포함하는 withCredentials 플래그를 담아서 보내거나, 웹 서버에서 access-control-allow-credentials를 true로 설정 한다면 인증 정보를 담아서 브라우저는 요청을 보낼 수 있습니다.
// Credentialed Request 보내는 XMLHttpRequest 간단 예제
const invocation = new XMLHttpRequest();
const url = 'http://bar.other/resources/credentialed-content/';
function callOtherDomain() {
if (invocation) {
invocation.open('GET', url, true);
invocation.withCredentials = true;
invocation.onreadystatechange = handler;
invocation.send();
}
}
// Credentialed Request에 대한 응답 header 간단 예제
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Credentials: true

발표해주세요 :)