킹의 개발일지
CORS 알고보면 착한 녀석! 본문
CORS 넌 뭔데 우릴 힘들게 하는거야...

웹 개발을 하다보면 필연적으로 만날 CORS에러.. 정성껏 API를 만들어 서버에 요청을 날리면 '안돼 돌아가' 하며 콘솔창에 빨간 에러 로그를 던져버린다. 하지만.. 이녀석이 고마운 녀석이라고??
우선 CORS를 이해하기전에 먼저 몇가지만 알고가자!
Origin(출처)
오리진, 한국어로 출처라 부르는 것은, 접근할 때 사용하는 URL의 스킴(프로토콜 ex. http, https), 호스트(도메인), 포트로 정의한다. 글로 보는것보단 실제 URL 구조를 보면 쉽게 이해할 수 있다.
https부터 8080까지를 Origin이라고 부른다. 왜 Orgin에 대해 먼저 설명했냐면... 바로 CORS 중 O가 바로 Origin의 O이기 때문이다!

CORS의 진짜 이름 Cross-Origin Resource Sharing
음, 이제 Origin은 알았는데, Cross Origin?, 해석해보면 출처가 다른 무언가 끼리 자원을 공유한다는 뜻 정도로 해석할 수 있겠는데, 음 이게 뭔 뜻일까?!
Cross Origin을 알아보기 전에 Same Origin을 먼저 알아보자. (CORS는 언제 설명할려고.. Same Origin을 알면 CORS가 왜 이렇게 우릴 괴롭히는지 자연스레 알수 있다!)
SOP(Same-Origin Policy)
Same Origin Policy, 동일 출처 정책, 즉 브라우저가 다른 출처의 리소스를 사용하는 것을 제한하는 보안 방식을 의미한다.
출처가 동일하다는 것은 다음과 같이 Origin이 동일하다는 의미다. 동일한 출처의 예시를 보자.
- http://example.com/app1/index.html
- http://example.com/app2/index.html
두 URL은 동일한 스킴과 호스트이름을 갖고 있기에 동일한 출처이다. 뒤에 붙는 파일 경로는 중요하지 않다.
- http://example.com:80
- http://example.com
서버는 기본적으로 포트 80을 통해서 콘텐츠를 전달하기에 두 출처는 동일하다.
- https://example.com
- http://example.com
스킴이 다르기 때문에 동일 출처가 아니다. (나중에 설명하겠지만 이런것을 Cross Origin이라 한다.)
- https://example.com
- https://www.example.com
- https://m.example.com
위 URL은 호스트 네임이 다르기 때문에 동일한 출처가 아니다.
이렇게 동일 출처, 교차 출처의 예시들을 살펴봤다. 그럼 왜 브라우저는 SOP를 통해서 같은 출처끼리만 리소스를 사용하도록 제한할까?
공격 예시
한 가지 예를 들어서 브라우저에 가해질 수 있는 공격을 보자.
- 사용자는 주홍.com에 로그인한다. 이 때 로그인 유지하기 기능을 사용하기에, 쿠키에 로그인 정보가 담긴다.
- 이 때 악의적인 유저가 사용자에게 자기가 만든 가짜 사이트에 접속하도록 유도한다. (팝업 창, 링크가 담긴 이메일 등..)
- 사용자는 악의적인 사이트에 접속하고 쿠키에 담긴 로그인 정보가 악의적인 유저에게 넘어간다.
- 쿠키의 특징으로, 쿠키는 모든 요청에 담겨서 전달된다. (옵션으로 같은 출처에만 보낼 수 있도록 할 수도 있다.)
- 악의적인 유저는 사용자에게서 빼낸 로그인 정보를 토대로 주홍.com에 계정 삭제, 물품 구입 등 서버에 악의적인 요청을 보낸다.
이렇게 악의적인 유저가 사용자에게서 빼낸 정보로 악의적인 요청을 서버에 보낼 수 있는데, 이때 SOP가 이를 방지해준다. 악의적인 유저의 출처는 당연히 서버의 출처와 다르기 때문에, 교차 출처로 SOP 정책에 의해 악의적인 유저의 요청은 거부 된다.
기본적으로 브라우저는, 위 처럼 출처가 다른 사이트끼리 요청이 못가겠끔 막아준다. 브라우저가 이 역할을 하기 때문에, POST MAN, 서버에서 다른 서버에 보내는 요청들은 허용이 됐던 것이다.
때문에 브라우저에선 다른 출처끼리 데이터 공유가 불가능하다. 허면 내가 만든 사이트에서 카카오 지도 API나 공공데이터 포털의 API를 사용하지 못하는 것일까? 아니다 이미 우리는 잘 쓰고 있지 않은가?
이를 가능하게 해주는 것이 CORS인것이다. 교차 출처 리소스 공유, 이제 이름의 의미가 살짝 다가오지 않는가?!
CORS 다른 출처간에 리소스 공유를 가능하게 해주는 녀석!
SOP를 보고나니 CORS가 왜 착한 녀석이라고 소개했는지 감이 잡힐 것이다.
그럼 어떻게 해야 이 CORS를 이용해서 서버간에 리소스를 공유할 수 있도록 할 수 있을까? 기본적으로 이 작업 백엔드에서 요구된다. 서버에서 리소스를 공유할 출처를 지정해두면 그 출처끼리는 교차 출처임에도 리소스 공유가 가능하다.
브라우저에서 CORS 동작 방식은 다음과 같다.
1. 브라우저는 요청을 보낼 때 Origin이라는 요청 헤더 옵션에 요청하는 곳의 Origin을 담아서 보낸다.
Request Headers
Method : GET
Origin : https://example.com
...
2. 서버는 응답 헤더에 Access-Control-Allow-Origin 옵션으로 자원 공유를 허용하고자 하는 출처를 담아 보낸다.
Response Headers
Access-Control-Allow-Origin : https://example.com
Content-Type : application/json
...
3. 브라우저는 두 출처가 동일하다면 자원 공유를 허용하고 다르다면 CORS를 허용하라는 에러 메세지를 띄운다.
브라우저가 교차 출처에 대해 자원 공유를 하는 방식을 알아봤으니, CORS의 동작 시나리오를 몇가지 살펴보자.
CORS 시나리오
1. Preflight Request (일반적으로 사용되는 방식)
Preflight는 사전적으로 '비행 전에 하는 장비 점검 등'을 의미하는데, 이 의미와 비슷하게 Preflight Request는 본 요청 전에 브라우저가 보내는 예비 요청이다. 때문에 실직적으로 보내는 요청은 두 번 일어난다!
이 예비 요청은 HTTP 메서드 중 OPTIONS 메서드를 사용하는데, 동작 방식은 다음과 같다.
- 브라우저가 본 요청에 앞서 'https://example-back.com/example-data' 에 OPTIONS 메서드로 Origin : http://example-front.com 헤더 옵션을 달아서 서버로 보낸다.
- 서버는 요청에 상태 코드와 함께, 응답 헤더 옵션으로 Access-Control-Allow-Origin : http://example-front.com 을 전달한다.
- 이후 브라우저는 받아온 두 출처가 동일하다면 본 요청을 보낸다.
여기서 서버에서는 Access-Control-Allow-Origin을 직접 명시할 수 도 있지만, '*' 즉 와일드 카드를 사용할 수 도 있다. 이 때 의미는 모든 출처에 대해서 자원을 공유해도 된다 라는 의미다. 이 방법은 애써 브라우저가 외부 공격을 방지하기 위해 만들어둔 방지책을 없애 버리는 행동이니 개발 단계에서만 사용하고 배포 버전에서는 허용하고자하는 Origin 꼭 명시 해주어야한다.
2. Simple Request
Simple Request는 Preflight와 다르게 예비 요청이 없다. 즉 본 요청과 함께 출처를 비교하게 되는데, 조건이 따른다.
- GET, HEAD 요청
- Content-Type이 다음과 같은 POST 요청
- application/x-www-form-urlendoed
- multipart/form-data
- text/plain
- Accept, Accept-Language, Content-Language, 등등 지정된 헤더 옵션 이외에는 사용할 수 없다.
사용할 수 있는 옵션이 너무 제한적이기에, (JWT 토큰도 못넣는다...),거의 대부분 Preflight Request를 사용한다.
3. Credentialed Request
쿠키나 세션 정보가 담긴 HTTP 요청에 대해서는 이 방법을 사용해서 처리한다. 출처를 비교하는 방식은 Preflight와 비슷한데, 차이점을 살펴보자
- 요청 헤더에 Cookie 같은 인증 정보가 담겨 전달된다.
- 서버는 Access-Control-Allow-Origin : * 을 사용할 수 없다.
- 서버는 Access-Control-Allow-Credential : true를 설정 해주어야 한다.
credentials 옵션으로는 다음 값들이 올 수 있다.
- same-origin(기본값): 같은 출처에만 인증 정보를 담는다.
- include: 모든 요청에 인증 정보를 담는다.
- omit: 모든 요청에 인증 정보를 담지 않는다.
이렇게 SOP, CORS가 무엇인지 알아보았고 왜 CORS가 생겼는지 대해서 알아 보았다. 실제 처리를 해줘야 할 곳은 서버측이지만 프론트엔드 개발자 또한 CORS가 무엇인지는 알고 있어야 한다고 생각했다.
단순히 CORS가 무엇인지 알고있는 것보단 왜 브라우저가 Same Origin에서만 리소스 공유를 허락하는지 그리고 어떤 방식으로 출처를 비교하는지 알고 있으면 웹 생태계를 이해하는데 큰 도움이 될것이다.