반응형
CORS가 뭔데!!
필자는 CORS 개념을 얼핏 들어보기만 했을뿐, 잘 몰랐엇다.
친구가 우연히 한 질문을 이해조차 못하는 나였고, 곧바로 CORS에 대해서 공부했다. 😂

이번 글에서는 CORS 개념을 이해하고 정리한다. 

 
 

CORS (Cross-Origin Resource Sharing)정의

우선, 위키피디아에 CROS를 검색해보았다.

CORS는, 웹 페이지 상의 리소스를 최초 자원이 서비스된 도메인 밖의 다른 도메인으로부터 요청할 수 있도록 허용하는 메커니즘이다.
하지만, cross-domain 간의 요청은 기본적으로 Same-Origin Security Policy 에 의해 기본적으로 금지된다.(CORS를 지키면 SOP를 위반하더라도 예외적으로 공유가 가능 하다는 것.)

반응형

쉽게 이야기하자면, 웹 생태계에는 다른 출처로의 리소스 요청을 제한하는 것과 관련된 두 가지 정책이 존재하는데,
한가지는 이번 글의 주제인 CORS, 그리고 또 한가지는, SOP(Same-Origin Security Policy) 정책이다.
 

SOP(Same-Origin Policy)

SOP는 2011년, RFC 6454 에서 처음 등장한 보안 정책으로 말그대로 "같은 출처에서만 리소스를 공유할 수 있다" 라는 규칙을 가진 정책이다.
그러나, 웹이라는 오픈스페이스 환경에서 다른 출처에 잇는 리소스를 가져와서 사용하는 일은 굉장히 흔한 일이라 무작정 막을수도 없는 노릇이니, 몇가지 예외 조항을 두고 이 조항에 해당하는 리소스 요청은 출처가 다르더라도 허용하기로 했는데,
그중 하나가 "CORS 정책을 지킨 리소스 요청" 이다. 

3.4.2.
Network Access Access to network resources varies depending on whether the resources are in the same origin as the content attempting to access them. Generally, reading information from another origin is forbidden. However, an origin is permitted to use some kinds of resources retrieved from other origins. For example, an origin is permitted to execute script, render images, and apply style sheets from any origin. Likewise, an origin can display content from another origin, such as an HTML document in an HTML frame. Network resources can also opt into letting other origins read their information, for example, using Cross-Origin Resource Sharing [CORS].

RFC 6454 - 3.4.2 Network Access

우리가 다른 출처로 리소스를 요청한다면 SOP 정책을 위반한 것이 되고, 거기다가 SOP 예외 조항인 CORS 정책까지 지키지 않는다면 아예 다른 출처의 리소스를 사용할 수 없게 되는 것이다.
즉, 이렇게 다른 출처의 리소스를 사용하는 것을 제한하는 행위는 하나의 정책만으로 결정된 사항이 아니라는 의미이며, SOP에서 정의된 예외 조항과 CORS를 사용할 수 있는 케이스들이 맞물리지 않을 경우에는 아예 리소스 요청을 할 수 없는 케이스도 존재할 수 있다.
근데 왜 이렇게 귀찮은 정책을 만들어서 신경쓰게 만드는 것일까? 개발자는 어차피 정해진 서버하고만 통신하도록 어플리케이션을 개발할텐데 말이다.
하지만, 잘 생각해보면, 출처가 다른 두 개의 어플리케이션이 마음대로 소통할 수 있는 환경은 꽤 위험한 환경이다.
클라이언트 어플리케이션, 특히 웹에서 돌아가는 클라이언트 어플리케이션은 사용자의 공격에 너무나도 취약하다.
당장 브라우저의 개발자 도구만 열어도 DOM 이 어떻게 작성되어있는지, 어떤 서버와 통신하는지, 리소스 출처는 어디인지와 같은 각종 정보들을 아무런 제재 없이 열람할 수 있다.
최근에는 자바스크립트 소스 코드를 난독화해서 읽기 어렵다고 하지만, 난독화는 난독화일뿐 암호화가 아니다.
그리고 난독화 되어있다고해도, 사람이 이해할 수 없는 정도도 아닌데다가, 소스 코드를 직접 볼 수 있다는 것 자체가 보안적으로 상당히 취약한 부분이다. 심지어 아직까지도 소스 코드의 난독화가 안되어 개발자 도구만 열면 <script> 태그 안에 날 것 그대로의 소스 코드가 떡하니 노출되는 사이트들도 많다.
이러한 상황 속에서 다른 출처의 어플리케이션이 서로 통신하는 것에 아무런 제약도 존재하지 않는다면, 악의를 가진 사용자가 소스 코드를 훑어본 후, CSRF(Cross-Site Request Forgery)XSS(Cross-Site Scripting) 와 같은 방법을 사용하여 어플리케이션에서 코드가 실행된 것 처럼 꾸며서 사용자의 정보를 탈취하기 매우 쉬워진다.
그러면 어플리케이션 개발자가 SOP, CORS를 신경쓰는 것 보다 훨씬 신경써야 할 일이 더 많아질 것이다.
Cross-Origin Resource Sharing 과 Same-Origin Security Policy 에 공통적으로 들어가 있는 Origin(출처) 라는 단어에 집중해보자. 출처(Origin) 라는 것이 정확히 뭘 의미하는지 파악하는 것이 중요할  것 같다.
 

출처(Origin)가 무엇인가?

서버의 위치를 의미하는 https://google.com 과 같은 URL들은 마치 하나의 문자열 같아 보이지만, 사실은 여러 개의 구성 요소로 이루어져 있다.

이때 출처는, ProtocolHost, 그리고 그림에서는 생략되어 있지만 :80, :443 과 같은 Port 번호까지 모두 합친 것을 의미한다.
Protocol + Host + Port : 출처 라고 이해하면 된다.
즉, 서버의 위치를 찾아가기 위해 필요한 가장 기본적인 것들을 합쳐 놓은 것이다.

3.3.2 http URL


If the port is empty or not given, port 80 is assumed. The semantics are that the identified resource is located at the server listening for TCP connections on that port of that host, and the Request-URI for the resource is abs_path (section 5.1.2).

또한 출처 내의 포트 번호는 생략이 가능한데, 이는 각 웹에서 사용하는 HTTP, HTTPS 프로토콜의 기본 포트 번호가 정해져있기 때문이다.
HTTP가 정의된 RFC 2616 문서를 보면 다음과 같이 기본 포트 번호가 함께 정의되어있는 것을 볼 수 있다.
만약, 포트가 생략되어 있지 않고 https://google.com:443 과 같이 출처에 포트 번호가 명시적으로 포함되어 있다면 이 포트 번호까지 모두 일치해야 같은 출처라고 인정된다. 하지만, 이 케이스에 대한 명확한 정의가 표준으로 정해진 것은 아니기 때문에, 어떤 경우에는 같은 출처, 어떤 경우에는 다른 출처로 판단될 수 있다. 이를인지하고 있도록 하자.
우리는 브라우저의 개발자 도구의 콘솔에서 Location 객체가 가지고 있는 origin 프로퍼티에 접근함으로써 손쉽게 어플리케이션이 실행되고 있는 출처를 알아낼 수도 있다.

console.log(location.origin);
https://mgyo.tistory.com/

 

같은 출처와 다른 출처의 구분

두 URL의 구성 요소 중 Protocol, Host, Port 이 3가지만 동일하면 같은 출처라고 판단한다.
https://mgyo.tistory.com:443 라는 출처를 예로 들면
https:// 이라는 Scheme(사용할 프로토콜을 의미)
mgyo.tistory.com 라는 Host
:443 이라는 Port 를 사용하고 있다는 것만 같다면 나머지는 전부 다르더라도 같은 출처로 인정이 된다는 것이다.
필자의 블로그 출처인 https://mgyo.tistory.com 와 같은 출처로 인정되는 예시는 대략 아래와 같다.

URL같은 출처이유
https://mgyo.tistory.com/aboutOScheme, Host, Port가 동일
https://mgyo.tistory.com/about?q=안녕OScheme, Host, Port가 동일
https://user:password@mgyo.tistory.comOScheme, Host, Port가 동일
http://mgyo.tistory.comXScheme이 다름
https://api.tistory.comXHost가 다름
https://mgyo.naver.comXHost가 다름
https://mgyo.tistory.ioXHost가 다름
https://mgyo.tistory.com:8000?브라우저의 구현에 따라 다름

맨 마지막 케이스의 경우, 만약 출처에 https://mgyo.tistory.com:443 처럼포트 번호가 명시되어 있다면, 명백하게 다른 출처로 분류되지만, 예시로 든 출처의 경우 포트 번호를 생략했기 때문에, 판단하기가 애매하다. RFC6454의 Comparing Origins 섹션에서는 "만약 출처가 Scheme/Host/Port 의 삼중 체계라면.." 이라는 전제가 붙어있기 때문에, 어떻게 해석하냐에 따라 구현이 달라질 있기 때문이다.
그래서 이러한 경우에는 보통 각 브라우저들의 독자적인 출처 비교 로직을 따라간다.

출처 비교시 Port를 완전 무시하는 브라우저는 Internet Explorer 밖에 없다고한다. ㅂㅂㅂ

여기서 중요한 사실 한 가지는 이렇게 출처를 비교하는 로직이 서버에 구현된 스펙이 아니라 브라우저에 구현되어 있는 스펙이라는 것이다.
만약 우리가 SOP정책을 위반하고 CORS정책도 위반하는 리소스 요청을 하더라도 해당 서버가 같은 출처에서 보낸 요청만 받겠다는 로직을 가지고 있는 경우가 아니라면 서버는 정상적으로 응답을 하고, 이후 브라우저가 이 응답을 분석해서 SOP정책 위반 예외 조건인 CORS 정책을 준수했는지 살펴보고, CORS 정책 위반이라고 판단되면 그 응답을 사용하지 않고, 브라우저에서 자체적으로 버리는 식이다.

https://mgyo.tistory.com - https://api.myo.io (출처가 다른 경우) [사진 출처:evan]

즉, CORS 는 브라우저의 구현 스펙에 포함되는 정책이기 떄문에, 브라우저를 통하지 않고 서버 간 통신을 할 때는 이 정책이 적용되지 않는다. 또한 CORS 정책을 위반하는 리소스 요청 떄문에 에러가 발생했다고 해도, 서버 쪽 로그에는 정상적으로 응답을 했다는 로그만 남기 때문에, CORS 동작방식을 제대로 모르면, Error Tracingt시에 난항을 겪을 수 있다.
CORS 동작 방식을 알아보자.
 

CORS 동작 과정

어떻게 서로 다른 출처를 가진 리소스를 안전하게 사용할 수 있는지 알아보자.
기본적으로 웹 어플리케이션이 다른 출처의 리소스를 요청할 때는 HTTP 프로토콜을 사용하여 요청을 보내게 되는데, 이때 브라우저는 요청 헤더에 Origin 이라는 필드에 요청을 보내는 출처를 함께 담아서 보낸다.

Origin: https://mgyo.tistory.com

이후 서버가 이 요청에 대한 응답을 할 때
응답 헤더의 Access-Control-Allow-Origin 이라는 값에 "이 리소스를 접급하는 것이 허용된 출처" 를 내려주고, 

이후 응답을 받는 브라우저는 자신이 보냇던 요청의 Origin과 서버가 보내준 응답의 Access-Control-Allow-Origin을 비교해본 후 이 응답이 유효한 응답인지 아닌지를 결정한다.
기본적인 흐름은 이렇게 간단하지만, 사실 CORS가 동작하는 방식은 한 가지가 아니라 세 가지의 시나리오에 따라 변경된다.
따라서 우리의 요청이 어떤 시나리오에 해당되는지 잘 파악한다면, CORS 정책 위반으로 인한 에러를 해결하는 것이 한결 쉬울 것이다.
 

Preflight Request

Preflight Request(프리플라이트) 방식은 일반적으로 우리가 웹 어플리케이션을 개발할 때 가장 자주 마주치는 시나리오이다.
이 시나리오에 해당하는 상황일 때 브라우저는 요청을 한번에 보내지 않고 예비 요청과 본 요청으로 나누어서 서버로 전송한다.
이때 브라우저가 본 요청을 보내기 전에 보내는 예비 요청을 Preflight 라고 부르는 것이며,
이 예비 요청에는 HTTP 메서드 중 OPTIONS 메서드가 사용된다.

예비 요청의 역할은 본 요청을 보내기 전에 브라우저 스스로 이 요청을 보내는 것이 안전한지 확인하는 것이다.
이 과정을 간단한 플로우 차트로 나타내보면 대략 이런 느낌이다.

브라우저는 본 요청을 보내기 전 예비 요청을 먼저 보내고, 요청의 유효성을 검사한다. [출처 : evan]

자바스크립트의 fetch API 를 사용하여 브라우저에게 리소스를 받아오라는 명령을 내리면 브라우저는 서버에게 예비 요청을 먼저 보내고, 서버는 이 예비 요청에 대한 응답으로 현재 자신이 어떤 것들을 허용하고, 어떤 것들을 금지하고 있는지에 대한 정보를 응답 헤더에 담아서 브라우저에게 다시 보내주게 된다.
이후 브라우저는 자신이 보낸 예비 요청과 서버가 응답에 담아준 허용 정책을 비교한 후, 이 요청을 보내는 것이 안전하다고 판단되면 같은 엔드포인트로 다시 본 요청을 보내게 된다. 이후 서버가 본 요청에 대한 응답을 하면 브라우저는 최종적으로 이 응답 데이터를 자바스크립트에게 넘겨준다.
이 플로우는 브라우저의 개발자 도구 콘솔에서도 간단하게 재현해볼 수 있다.
필자의 github 블로그인 https://mgyokim.github.io/ 에서 필자의 티스토리 블로그인 https://mgyo.tistory.com 의 RSS 파일 리소스에 요청을 보내면 브라우저가 본 요청을 보내기 전에 OPTIONS 메서드를 사용하여 예비 요청을 보내는 것을 확인할 수 있다.

const headers = new Headers({
  'Content-Type': 'text/xml',
});
fetch('https://mgyo.tistory.com/rss', { headers });

콘솔에서 위와같이 작성하면,

OPTIONS https://mgyo.tistory.com/rss

Accept:
*/*
Accept-Encoding:
gzip, deflate, br
Accept-Language:
en-US,en;q=0.9,ko;q=0.8,ko-KR;q=0.7
Access-Control-Request-Headers:
content-type
Access-Control-Request-Method:
GET
Connection:
keep-alive
Host:
mgyo.tistory.com
Origin:
https://mgyokim.github.io
Referer:
https://mgyokim.github.io/
Sec-Fetch-Dest:
empty
Sec-Fetch-Mode:
cors
Sec-Fetch-Site:
cross-site

실제로 브라우저가 보낸 예비요청을 보면, 단순하 Origin 에 대한 정보 뿐만 아니라, 자신이 예비 요청 이후에 보낼 본 요청에 대한 다른 정보들도 함께 포함되어 있는 것을 볼 수 있다.
이 예비 요청에서 브라우저는 Access-Control-Request-Header를 사용하여 자신이 본 요청에서 Content-Type 헤더를 사용할 것을 알려주고, Access-Control-Request-Method를 사용하여 이후 GET 메서드를 사용할 것을 서버에게 미리 알려주고 있는 것이다.
이렇게 티스토리 서버에 예비 요청을 보내면, 이제 티스토리 서버가 이 예비 요청에 대한 응답을 보내준다.

Request URL:
https://mgyo.tistory.com/rss
Request Method:
OPTIONS
Status Code:
200 OK

Access-Control-Allow-Origin:
https://mgyo.tistory.com
Content-Encoding:
gzip
Content-Length:
17548
Content-Type:
text/xml; charset=utf-8
Date:
Wed, 21 Jun 2023 08:31:04 GMT
P3p:
CP='ALL DSP COR MON LAW OUR LEG DEL'
Vary:
Accept-Encoding
X-Ua-Compatible:
IE=Edge

여기서 눈여겨 봐야할 것은, 서버가 보내준 응답 헤더에 포함된 Access-Control-Allow-Origin: https://mgyo.tistory.com 이다. 
티스토리 서버는 이 리소스에 접근이 가능한 출처는 오직 https://mgyo.tistory.com 뿐이라고 브라우저에게 이야기 해준 것이고,
필자가 이 요청을 보낸 출처는 https://mgyokim.github.io/ 이므로 서버가 허용해준 출처와는 다른 출처이다.
그래서 결국 브라우저는 이 요청이 CORS 정책을 위반했다고 판단하고 다음과 같은 에러를 뱉게 되는 것이다.

잘 생각해보면, 예비 요청에 대한 응답에서는 에러가 발생하지 않고 응답으로 Status Code: 200 OK 로 떨어졌었다. 
그런데 콘솔 창에는 이렇게 빨갛게 에러가 표시되기 때문에 헛갈릴 수 있다.|

CORS 정책 위반으로 인한 에러는 예비 요청의 성공 여부와는 별 상관이 없다. 
브라우저가 CORS 정책 위반 여부를 판단하는 시점은 예비 요청에 대한 응답을 받은 이후이기 때문이다.
물론 예비 요청 자체가 실패해도 똑같이 CORS 정책 위반으로 처리될 수도 있지만,
중요한 것은 예비 요청의 성공/실패 여부가 아니라 "응답 헤더에 유효한 Access-Control-Allow-Origin 값이 존재하는가"이다. 

만약 예비 요청이 실패해서 StatusCode가 200 이 아닌 상태 코드가 내려오더라도,
예비요청에 대한 응답 헤더에 Access-Control-Allow-Origin 값이 예비요청의 Origin에 유효한 값이 제대로 들어가 있다면
CORS 정책 위반이 아니라는 의미이다.
대부분의 경우 이렇게 예비 요청, 본 요청을 나누어 보내는 Preflight Request 방식을 사용하기는 하지만, 
모든 상황에서 이렇게 두 번씩 요청을 보내는 것은 아니다. 조금 까다로운 조건이기는 하지만, 어떤 경우에는 예비 요청 없이 본 요청만으로 CORS 정책 위반 여부를 검사하기도 한다.
 

Simple Request

Simple Request(단순요청) 을 알아보자.
Simple Request는 예비 요청을 보내지 않고 바로 서버에게 본 요청부터 때려박은 후,
서버가 이에 대한 응답의 헤더에 Access-Control-Allow-Origin 과 같은 값을 보내주면
그때 브라우저가 CORS 정책 위반 여부를 검사하는 방식이다. 
즉, Preflight Request와 Simple Request 시나리오는 전반적인 로직 자체는 같되, 예비 요청의 존재 유무만 다르다.

Simple Reqeust는 예비 요청없이 바로 본 요청을 하는 시나리오다.

하지만, 아무 때나 단순 요청을 사용할 수 있는 것은 아니다. 특정 조건을 만족하는 경우에만 예비 요청을 생략할 수 있다.
게다가 이 조건이 조금 까다롭기 때문에 일반적인 방법으로 웹 어플리케이션 아키텍처를 설계하게 되면 거의 충족시키기 어려운 조건들이라 잘 경험하지 못할 수 있다.
조건은 다음과 같다.

  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 만 허용된다.

 
1번 조건의 경우는 PUT 이나 DELETE 같은 메서드를 사용하지 않으면 되는 것 뿐이므로 그렇게 드문 상황은 아니지만,
2번이나 3번 조건 같은 경우는 조금 까다롭다.
저 조건에 명시된 헤더들은 정말 기본적인 헤더들이기 때문에, 상용 웹 어플리케이션에 이 헤더들 외에 추가적인 헤더를 사용하지 않는 경우는 드물다. 사용자 인증에 사용되는 Authorization 헤더 조차 저 조건에는 포함되지 않는다.
게다가 대부분의 HTTP API는 text/xml 또는 application/json 컨텐츠 타입을 가지도록 설계되기 때문에 사실상 이 조건들을 모두 만족시키는 상황을 만들기는 쉽지 않다.
 

Credentialed Request

Preflight Request, Simple Request 시나리오에 이어 3번째 시나리오는 Credentialed Request 이다.
인증된 요청을 사용하는 방법이다. 이 시나리오는 CORS의 기본적인 방식이라기 보다는 다른 출처 간 통신에서 좀 더 보안을 강화하고 싶을 때 사용하는 방법이다.
기본적으로 브라우저가 제공하는 비동기 리소스 요청 API인 XMLHttpReqeust 객체나 fetch API 는 별도의 옵션 없이 브라우저의 쿠키 정보나 인증과 관려된 헤더를 함부로 요청에 담지 않는다.
이때 요청에 인증과 관련된 정보를 담을 수 있게 해주는 옵션이 바로 credentials 옵션이다.
이 옵션에는 총 3가지의 값을 사용할 수 있으며, 각 값들이 가지는 의미는 다음과 같다.

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

만약 same-origin 이나 include 같은 옵션을 사용하여 리소스 요청에 인증 정보가 포함된다면, 이제 브라우저는 다른 출처의 리소스를 요청할 때 단순히 Access-Control-Allow-Origin 만 확인하는 것이 아니라 좀 더 빡빡한 검사 조건을 추가하게 된다.
same-origin 이나 include 같은 옵션을 사용하면 어떤 제약이 . https://mgyo.tistory.com/ 에서 https://mgyokim.github.io/ 와의 통신을 통해 직접 확인해보도록 하겠다.
https://mgyokim.github.io/ Access-Control-Allow-Origin 값으로 모든 출처를 허용한다의미인 * 가 설정되어 있지 때문에, 다른 출처에서 https://mgyokim.github.io/ 로 리소스를 요청할 때 CORS 정책 위반으로 인한 제약을 받지 않는다.
그래서 fetch API 를 사용하여 마음대로 리소스를 요청하고,  받아올 수 있다.

fetch('https://mgyokim.github.io');
Request

GET https://mgyokim.github.io

Origin:
https://mgyo.tistory.com

Referer:
https://mgyo.tistory.com/
Response

GET https://mgyokim.github.io/

Access-Control-Allow-Origin:
*

Content-Encoding:
gzip

Content-Length:
1955

Content-Type:
text/html; charset=utf-8

Server:
GitHub.com

Status Code:
200

또한 구글 크롬 브라우저의  credentials 기본 값은 같은 출처 내에서만 인증 정보를 사용하겠다는 same-origin 이기 때문에,
https://mgyo.tistory.com/ 에서 https://mgyokim.github.io/로 보내는 리소스 요청에는 당연히 쿠키와 같은 인증 정보가 포함되어 있지 않다.

그렇기 때문에 브라우저는 단순히 Access-Control-Allow-Origin: * 이라는 값만 보고 "이 요청은 안전하군!!" 이라는 결론을 내리는 것이다.
그치만, https://mgyo.tistory.com/ 에서 요청을 보낼 때 credentials 옵션을 모든 요청에 인증 정보를 포함하겠다는 의미를 가진 include 로 변경하고 같은 요청을 보내면 상황이 달라진다.

fetch('https://mgyokim.github.io', {
  credentials: 'include', // Credentials 옵션 변경!
});

직접 브라우저 콘솔에서 실행해보면 알겠지만, 이번에는 credentials: include 옵션을 사용하여 동일 출처 여부와 상관없이 무조건 요청에 인증 정보가 포함되도록 설정했으므로, 이번 요청에는 브라우저의 쿠키 정보가 함께 담겨있는 것을 확인해볼 수 있다.
 
https://mgyokim.github.io/ 서버는 이번에도 동일한 응답을 보내주었지만, 브라우저의 반응은 다르다.

Access to fetch at 'https://mgyokim.github.io/' from origin 'https://mgyo.tistory.com' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. 
를 해석해보면, 
CORS 정책에 의해 접근이 차단되었고, 요청의 credentials가 'include' 일 경우, Access-Control-Allow-Origin 헤더의 값으로 모든 요청을 허용한다는 의미인 * 를 사용하면 안된다고 이야기 하고 있다.
이처럼 요청에 인증 정보가 담겨있는 상태에서 다른 출처의 리소스를 요청하게 되면 브라우저는 CORS 정책 위반 여부를 검사하는 룰에 다음 두 가지를 추가하게 된다.

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

지금까지 알아본 3가지 시나리오를 잘 알아두자. CORS 정책 위반으로 인한 문제가 발생했을 경우 해결 시간을 크게 단축 시킬 수 있을 것이다.
 

CORS 문제 해결 방법

지금까지 CORS가 무엇인지, 시나리오 등을 알아보았다.
이번에는 실제로 CORS 정책 위반으로 문제가 발생했을 경우에 해결할 수 있는 방법을 알아보도록 하겠다.

Access-Control-Allow-Origin 세팅하기

CORS 정책 위반으로 인한 문제를 해결하는 가장 대표적인 방법은, 그냥 정석대로 서버에서 Access-Control-Allow-Origin 헤더에 알맞은 값을 세팅해주는 것이다.
이때  *  을 사용하여 세팅하게 되면 , 모든 출처에서 오는 요청을 받아먹겠다는 의미이므로 당장은 편할 수 있겠지만, 바꿔서 생각하면, 정체도 모르는 이상한 출처에서 오는 요청까지 모두 받아먹겠다는 것과 다를 것이 없으므로 보안적으로 심각한 이슈가 발생할 수도 있다.
그러니 가급적이면 귀찮더라도 Access-Control-Allow-Origin: https://mgyo.tistory.com/ 처럼 출처를 명시해주도록 하자.
이 헤더는 Nginx나 Apache 같은 서버 엔진의 설정에서 추가할 수도 있지만, 아무래도 복잡한 세팅을 하기에는 불편하기떄문에,
소스 코드 내에서 응답 미들웨어 등을 사용하여 세팅하는 것을 추천한다.
Spring, Django 와 같이 유명한 백엔드 프레임워크의 경우에는 모두 CORS 관련 설정을 위한 세팅이나 미들웨어 라이브러리를 제공하고 있으니 세팅은 어렵지 않을 것이다.
 
 
 
 
 

마무리

 
처음으로 CORS 정책 위반으로 인해 생기는 문제를 해결할 때 가장 혼란스러운 것이, 문제를 겪는 사람과 문제를 해결해야하는 사람이 다르다는 것이다.
앞서 이야기했듯, CORS 정책은 브라우저의 구현 스펙이기 때문에, 정책 위반으로 인해 문제를 겪는 사람은 대부분 프론트엔드 개발자이지만, 정작 문제를 해결하기 위해서는 백엔드 개발자가 서버 어플리케이션의 응답 헤더에 올바른 Access-Control-Allow-Origin 이 내려올 수 있도록 세팅해줘야 하기 때문이다.
사실, 프론트쪽에서 webpack-dev-server 의 프록싱 옵션을 사용하여 자체적으로 해결하는 방법도 있긴하지만, 이 방법은 로컬 개발 환경에서만 통하는 방법인데다가, 근본적인 문제 해결 방법이 아니기 때문에 결국 운영 환경에서 CORS 정책 위반 문제를 해결하기 위해서는 백엔드 개발자의 도움이 필요할 수 밖에 없다.
CORS 정책 위반을 해결하는 방법 자체가, 사실 그리 어려운 것은 아니라서, 프론트 개발자나 백엔드 개발자 중 한명이라도 이 정책에 대해서 잘 알고 있는 경우라면, 이슈 포인트를 빠르게 파악하여 수월하게 문제를 해결할 수 있을 것이다.

친구가 나에게 이 질문을 했었을 때의 나였다면, 문제 해결에 시간이 오래 걸렸겠지만,
이제는 이슈포인트를 이해하여 빠르게 해결할 수 있을 것 같다. 
이만 "너 CORS 설정 해줬어?" 포스팅을 마친다. 

반응형