본문 바로가기
📍 CS/Network

CORS는 무엇인가?!

by briee 2021. 7. 10.
CORS(Cross-Origin Resource Sharing)

CORS는 다른 Origin(출처) 간의 리소스 공유를 허용할 수 있게 해주는 것이다. 

그럼 출처가 무엇인지, CORS가 어떻게 동작하는지 알아보도록 하자.

 


 

Origin(출처):

 

웹 콘텐츠에 접근을 할 때 우리는 URL을 통해 접근한다. 이때 URL은 이런 구성요소를 갖는다.

구성요소 중에서 Scheme(Protocol), Host, Port 이 세 가지가 바로 출처를 의미한다. (HTTP 프로토콜에서 포트 번호를 명시하지 않으면, 80번 포트를 기본 값으로 사용한다.)

 

 

그리고 이렇게 브라우저 개발자 도구의 콘솔에서 location 객체가 가지고 있는 origin 프로퍼티에 접근함으로써 실행되고 있는 출처를 알아낼 수도 있다.

 

 

SOP (Same-Origin Policy): 

 

말그대로 같은 출처에서만 리소스 공유를 허용하겠다는 정책이다. 앞서 말했듯이 출처는 Scheme(Protocol), Host, Port를 의미하는데 두 URL을 비교해서 이 세 가지가 모두 같다면 같은 출처라고 한다. 하지만 웹이라는 환경에서 다른 출처에 있는 리소스를 가져와서 사용하는 일은 흔한 일이라 SOP정책에 예외 조항이 있다. 그 예외 조항 중 하나가 CORS 정책을 지킨 리소스 요청인 것이다. CORS 정책을 지키지 않고 다른 예외조항에도 맞지 않는다면 SOP에 완전히 위반한 것이 되어 다른 출처에 있는 리소스를 사용할 수 없게 된다.

 

이러한 방법을 사용하는 이유는 악의를 가진 해커로부터 인터넷을 보호하기 위해 만들어졌다. 지금도 브라우저의 개발자 도구만 열어도 많은 정보들을 열람할 수 있기 때문이다. 그래서 이런 제약이 없다면  CSRF(Cross-Site Request Forgery) XSS(Cross-Site Scripting) 같은 방법을 이용해 사용자의 정보를 손쉽게 탈취할 수 있다. 또한 서버는 CORS를 위반하더라도 정상적으로 응답을 해주고 중간에 브라우저가 응답의 파기 여부를 결정하는 것이다.

 


 

CORS 동작 방법:

 

웹 애플리케이션은 리소스가 자신의 출처(도메인, 프로토콜, 포트)와 다를 때 교차 출처 HTTP 요청을 실행하는데 이때 브라우저는 요청 헤더의 Origin 이라는 필드에 요청을 보내는 출처를 함께 담아 보낸다.

 

Origin: https://bribrie.tistory.com

 

이 요청에 대해 서버는 응답 헤더의 Access-Control-Allow-Origin이라는 값에 “이 리소스를 접근하는 것이 허용된 출처”를 내려주고, 이후 응답을 받은 브라우저는 요청의 Origin과 서버가 보내준 응답의 Access-Control-Allow-Origin을 비교해본 후 이 응답이 유효한 응답인지 아닌지를 결정한다. 기본적인 흐름은 이러하며 동작하는 방식은 세 가지 시나리오가 있다. 

 

 

Simple Request (단순 요청):

 

 

단순 요청은 그림과 같이 바로 서버에게 Origin이라는 헤더를 추가한 요청을 보내 응답 헤더를 받아 브라우저가 CORS 정책 위반 여부를 검사하는 방법이다. 이러한 단순 요청을 보내기 위해서는 아래의 세 가지 조건을 만족해야 한다.

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 중 하나여야 한다.

 

하지만 사실상 이 조건들을 모두 만족시키는 상황을 만들기는 쉽지 않다고 한다. 요청의 메서드만 해도 DELETE 같은 메서드도 사용할 수 있으며, Content-Type 또한 application/json이나 text/xml을 사용하는 경우가 더 많기 때문이다. 

 

 

Preflight Request (프리플라이트 요청):

 

 

프리플라이트 방법은 브라우저가 요청을 한 번에 보내지 않고, 예비 요청과 본 요청으로 나누어서 서버로 보낸다. 이중 예비 요청을 프리플 라이트라고 부르는 것이다. 

 

프리플라이트 요청 방법에서는 우선 브라우저가 리소스를 받아오라는 명령을 받으면 서버에게 예비 요청을 보낸다. 이때 메서드는 OPTIONS를 사용하며 그림에서 보이는 헤더는 아래와 같은 뜻이다.

Origin – 본 요청의 오리진
Access-Control-Request-Method – 본 요청에서 사용하는 메서드
Access-Control-Request-Headers – 본 요청에서 사용하는 헤더 목록(콤마로 구분)

 

이제 서버가 이 예비 요청에 대해 응답을 보내주는데 어떤 것을 허용하고 금지하는지 정보를 응답 헤더에 담아 브라우저에 보내준다. 이때 주의할 점은 서버는 요청에 대한 응답을 잘 보내주고 상태 코드 200을 준다. 하지만 브라우저가 이 응답을 받고 CORS 위반 여부를 파악하기 때문에 상태코드 200이라 하더라도 CORS 에러를 만날 수 있다. 

 

이후 브라우저가 예비 요청과 서버의 응답을 비교한 후, 이 요청을 보내는 것이 안전하다고 판단되면 같은 엔드포인트로 다시 본 요청을 보낸다. 그리고 서버가 이 본 요청에 대한 응답을 하면 브라우저는 최종적으로 이 응답 데이터를 자바스크립트에게 넘겨준다.

이렇게 예비 요청을 사용하는 이유는 서버의 데이터에 영향을 줄 수 있는 요청들이기 때문에 먼저 허용 여부를 검증할 수 있기 때문이다.

 

 

Credentialed Request (자격증명 요청):

 

기본적으로  XMLHttpRequest 객체나 fetch API는 별도의 옵션 없이 브라우저의 HTTP cookies와  HTTP Authentication 정보를 함부로 요청 헤더에 담지 않는다. 하지만 이 자격증명 요청은 이 정보를 인식하도록 한다. 이렇게 인식하도록 만드는 옵션은 credentials이다. 이 옵션의 값으로는 세 가지를 사용할 수 있다.

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

 

이렇게 옵션을 추가해 요청을 보내게 되면 쿠키가 함께 전송된다. 이후 이 요청을 서버에서 허용한다면 응답 헤더에 Access-Control-Allow-Credentials: true를 추가해 응답을 보내준다. 이 방법에서 주의할 점은 Access-Control-Allow-Origin: * (와일드카드, 모든 출처를 허용한다는 뜻) 은 사용할 수 없고, 정확한 오리진 정보를 명시해야 한다. 이유는 서버가 어떤 오리진에서 요청이 왔는지에 대한 정보를 신뢰할 수 있기 때문이다.

 

이렇게 자격증명이란 예외가 생긴 이유는 이 정보들이 민감한 정보이기 때문에 영향력이 크기 때문이다. 

 

 

CORS해결방법:

 

* 서버에서 Access-Control-Allow-Origin 헤더에 알맞은 값을 세팅해주기 (나는 express의 미들웨어 라이브러리를 통해 설정했었다.)

* Webpack Dev Server로 리버스 프록싱하기 (아직 사용해본 적이 없어서 더 공부해보고 추가하겠다.)

 

 


 

마무리하며

 

나 또한 프로젝트를 하면서 CORS에러를 만난 적이 있다. 분명 부트캠프하면서 배운 내용인데도 헷갈려서 다시 구글링을 하며 어렵게 해결한 기억이 있다. CORS에러는 개발하면서 쉽게 만날 수 있는 에러라고 한다. 그런데 그때그때 찾아보면서 한다면 비효율적이고 그건 아니다 싶어 한 번쯤 꼭 정리해서 내 머릿속에 쾅! 해놓아야겠다고 생각해서 정리를 해보았다. 참고한 EVANS님의 블로그가 잘 되어 있어서 이해하는데 수월했다. 나름 깔끔하게 정리하고자 mdn, 유튜브 등을 찾아보았지만 에반스님의 블로그를 가장 많이 참고한 것 같다. 결론적으로 이렇게 정리하면서 확실히 내 걸로 공부한 느낌이고, CORS에러를 만난다면 이제는 '아 CORS에러네..! 요롷게조롷게 해야겠다 ' 하면서 해결할 수 있을 것 같다. :) 

 

 

참고: 

'📍 CS > Network' 카테고리의 다른 글

Cookie & Session 정리  (0) 2021.07.13