웹 개발을 하면서 CORS 에를 접하지 않은 개발자는 없을 것이다. 개인적으로 만드는 아주 간단한 토이 프로젝트가 아닌 이상 실제로 서비스를 제공하는 웹 애플리케이션이라면 말이다.
이러한 문제가 다른 서버의 리소스에 대한 접근과 관련된 브라우저 정책과 관련되어 있다는 점과 웹 서버 설정을 변경해서 해결할 수 있다는 점 정도만 기억하지 세부적인 내용은 늘 잊어버린다. 다시 조사하고 읽어보면 그때만 잠깐 이해하고… 다시 까먹는다.
아마 이게 개발을 시작하면서 개발환경 구축 시점에, 혹은 배포 시 실제 환경에서 구축 초반에 일어나고 개발 및 배포의 전체 과정에서 빈번히 일어나는 문제가 아니여서….라기 보다는 그냥 머리가 나빠서 그런 것 같다.
간단하게라도 필요한 부분은 정리해둔다.
CORS 정의
CORS는 Cross-Origin Resource Sharing
의 약자이다. 단어 자체가 오류를 뜻하는 것은 아니고 하나의 매커니즘을 뜻하는 단어일 뿐이다. 즉 다른 출처(Cross-Orgin)의 리소스를 공유하는 법을 뜻하는 단어이고 그러한 정책을 위반하는 경우, 정책 위반 오류를 만나게 되는 것이다.
관련된 몇 가지 개념
이해를 위한 몇 가지 개념들을 먼저 정리 해보자.
Orign
Cross-Origin에서 얘기하는 Origin은 쉽게 말해 그냥 논리적인 웹서버를 뜻한다. 웹 서버가 가지고 있는 리소스가 아닌 웹 서버 자체를 가리킨다는 것은 사용하는 URI에서 프로토콜(스킴), 호스트명, 포트까지 정보를 뜻한다. 그 이후에 있는 Path, Query String, Anchor들은 리소스를 가리키는 영역이기 때문에 해당되지 않는다. (IE라는 이제는 전설이 된 브라우저는 포트번호는 체크 안한다는 소문이 있다.)
SOP(Same-Origin Policy)
웹에서 기본적인 규칙 중에 하나인 SOP는 같은 출처, 즉 같은 서버에서만 리소스를 공유할 수 있다는 규칙이다. 보안을 위해서는 당연한 규칙이기도 하고 강력한 규칙이 되겠지만, 실 세계는 전혀 상황이 다르다. 웹에서 다른 서버의 리소스를 가져와서 사용하는 경우는 너무나 빈번한 일이고 요즘에는 거의 필수적이기도 하다. 다른 서버에서 RESTful API를 통해서 데이터 오는 일은 거의 항상 일어나기도 하고 OAuth, CDN을 이용하는 경우나 하다못해 다른 사이트의 이미지나 동영상을 공유하는 등등 SOP만으로는 해결이 안되는 경우가 현실에는 너무 많다.
그렇게 SOP에 대한 예외 조항 중에 하나가 CORS
를 지키는 요청에 대해서는 허용하는 것이다.
웹 브라우저
웹 브라우저 얘기를 갑자기 하는 이유는 이러한 CORS 규칙을 체크하는 것이 웹 서버가 아니라 브라우저의 역할이기 때문이다. 생각해보면 당연한 일이기도 하다. 해킹을 위해서 엉뚱한 서버로 접속하게 하는데 서버에서 체크하는 규칙이라면 엉뚱한 서버가 제대로 체크를 할리가 만무하지 않은가?
몇 가지 시나리오를 통해서 CORS를 적용하고 정책을 위반 했는지 체크하는 것은 웹 브라우저의 역할이란 점을 기억하자.
한 가지 더 기억할 점은 이런 CORS 정책 체크를 웹 브라우저가 하기 때문에 서버에서 돌아가는 로직을 구현할때는 적용되지 않는다.
CROS 처리 시나리오
가장 기본이 되는 시나리오는 아래와 같다.
1. 클라이언트(브라우저)는 요청 메시지 전송 시 “Origin”이라는 헤더 정보에 호출하는 프로그램의 출처를 기록한다.
2. 서버는 요청에 대한 응답을 보낼 때 “Access-Control-Allow-Origin”이라는 필드에 서버의 정책을 표현해서 응답한다.
3. 클라이언트는 이렇게 2개의 값을 이용해서 CORS 정책의 위반 여부를 판단한다.
큰 그림에서는 위의 기본적인 흐름을 유지하지만 세부적으로는 3가지 정도의 시나리오가 존재한다.
Simple Request
브라우저는 서버에게 요청을 보내고 응답을 받으면 그 응답의 헤더에 있는 “Access-Control-Allow-Origin” 필드를 체크해서 응답을 사용할지 CORS 정책 오류를 뱉을지 결정하는 기본 시나리오와 동일하다.
시나리오가 간단해서 MDN에서 “Somple Request”라는 표현을 사용한 것이지만 사실은 꽤나 엄격하게 체크한다.
자세한 내용은 아래 참고 사이트 링크에서 확인할 수 있지만 간단하게 얘기하면
1. GET, HEAD, POST 메소드만 사용할 수 있고
2. 헤더에 몇가지 필드만 사용 가능하다.
3. 게다가 “Content-Type” 헤더에 사용할 수 있는 값도 제한적이다.
이러한 몇 가지 조건들을 만족해야만 사용할 수 있어서 현실적으로 제한이 많은 편이다.
Preflight Request
“Preflight” 사전 비행 정도 되려나? 아무튼 이 시나리오는 가장 많이 사용하는 시나리오인데 실제 요청을 보내기 전에 CORS 정책을 확인하기 위한 메시지 교환을 미리 한번 하는 시나리오이다.
웹 브라우저는 사전 요청을 위해서 HTTP Method 중에서 “OPTION”이라는 메소드를 이용해서 보내고 기본적인 흐름처럼 응답의 “Access-Control-Allow-Origin” 필드를 이용하여 정책 준수 여부를 판단한다. 준수가 안되면 CORS 정책 위반 오류를 내고 괜찮으면 원래 요청을 진행한다.
메시지 전송이 2번 이루어지는 것은 부담이지만 캐시를 이용해서 최적화하기도 해서 제일 많이 사용하는 패턴이다.
Credentialed Request
이름부터가 뭔가 좀 더 보안을 강화할 것 같지 않은가?
헤더의 “credentials”에 아래와 같은 값을 설정해서 요청 정보에 인증 정보를 포함여부를 결정하는 형식으로 simple-origin, include, omit 3가지의 값을 가질 수 있다.
1. simple-origin : 같은 출처에 대한 요청에서만 인증 정보를 담을 수 있다.
2. include : 모든 요청에 대해서 인증 정보를 담을 수 있다.
3. omit : 모든 요청에 대해서 인증 정보를 담을 수 없다.
여기서 얘기하는 인증정보는 대표적으로 쿠기정보와 같은 인증과 관련된 내용을 포함하는 정보이다. 예를 들어 크롬은 기본적으로 “simple-origin” 값을 가지고 있는데, 이런 경우에는 서버가 “Access-Control-Allow-Origin:*”와 같은 값을 보내면, 즉 모든 외부 요청을 허락하겠다고 하면 fetch를 통한 API를 이용해서 리소스 접근이 가능하다.
CORS 오류 해결 시나리오
해결 시나리오라는 거창한 이름이긴 하지만 사실… 내가 생각하는 가장 정석적인 방법은 프록시를 사용하거나 크롬 플러그인을 사용하는 방법은 아니라고 생각한다. 개발 과정에서 편의를 위해서 적용은 할 수 있겠으나 궁극적으로는 정책을 고민하고 서버에서 적절한 수준의 설정을 필요로 한다.
위의 내용은 내가 정리하면서 적은 글이라 사실 다른 이들의 이해를 돕기에는 표현이 좀 이상하고 예제도 적절하지 않을 수 있다.
아래는 CORS 자체를 이해하기에 너무 훌륭한 글이랑 남겨둔다.
참고 사이트
교차 출처 리소스 공유 (CORS) – HTTP | MDN교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)