프린세스 다이어리

CORS 의미 / 현업에서 직접 겪은 CORS 이슈와 해결방법 정리 본문

FE

CORS 의미 / 현업에서 직접 겪은 CORS 이슈와 해결방법 정리

개발공주 2021. 10. 6. 12:00
728x90

입사 초기에 잘 몰라서 해결하는 데 오래 걸렸던 이슈였다. 사실 지금 생각하면 해결점이 꽤나 뚜렷한 초보적인 고민이었는데 당시에는 대체 누가 이걸 해결해야 하나 구글만 뒤지고 있었다. preflight이 뭔지도 모르고, 도대체 OPTIONS는 쓰지도 않았는데 왜 계속 등장하는지 서로 익숙지 않아 애를 썼던 기억이 난다. 브라우저의 보안에 대해 잘 알고 있었다면 문제없이 해결할 수 있었을 거다.

1. CORS란


먼저 CORS 가 가능하기 전 브라우저가 공격으로부터 보호하기 위해 동일 출처 정책(SOP)을 가지고 엄격하게 제약을 걸었다는 것을 짚고 넘어가야 한다. 브라우저에 대한 공격은 크게 능동적 공격과 수동적 공격으로 분류된다. 능동적 공격은 해커가 웹서버에 직접 공격하는 것을 뜻하고, 수동적 공격은 웹사이트에 함정을 파 놓고, 함정을 방문하는 사용자를 통해 브라우저를 공격하는 것을 말한다. 출처가 다른 사이트 간에 통신이 일어나는 경우 보안이 취약해지기 때문에, 웹브라우저는 동일 출처 정책으로 교차 출처를 막았다. 동일 출처 정책에 대해 정리한 글

하지만 브라우저에서 동일 출처 정책 제한을 넘어 타 사이트 간 데이터 교환 요구가 강해졌다. 이 때문에 XMLHttpRequest 등 여러 방법으로 사이트 간 데이터를 교환할 수 있는 사양인 CORS(Cross-Origin Resource Sharing)가 만들어졌다. CORS란, 기존의 동일 출처 정책에 의존하는 응용 프로그램의 호환성을 유지하면서 다른 출처의 자원을 공유할 수 있도록 한다. CORS 에러를 맞닥뜨렸을 때는 당장 데이터를 받아오지 못해 CORS가 작업을 못하게 방해한다고 생각했는데, 오히려 보안을 지키면서 동시에 교차 출처를 가능하도록 하는 사양이었던 것이다.

2. CORS 접근 제어 시나리오


교차 출처 리소스 공유가 동작하는 방식을 보여주는 세 가지 시나리오가 있다. 간단한 요청(Simple Request), 사전 점검 요청(Preflight Request), 인증 정보를 포함한 요청(Credentialed Request)이다. 각각에 대해 정리해보았다.

2-1. 간단한 요청


특정 조건을 만족하는 요청의 경우에는 허락 없이도 다른 출처에 HTTP 요청을 보낼 수 있다. 이 '간단한 요청' 방식은 바로 요청을 보내면서 해당 요청이 교차 출처인지 확인하는 것이다. 심플한 만큼 이 방식을 이용하는 데는 다음 세 가지 조건에 만족해야 간단한 요청을 바로 날릴 수 있고, 그러지 못한 경우 프리플라이트 요청을 먼저 날리고 허락을 받고 나서 본 요청을 보내야 한다.

Methods
- GET
- POST
- HEAD 중 하나
Content-Type
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain 중 하나
Headers
- Accept
- Accept-language
- Content-Language
- Content-Type 중 하나

 

2-2. 사전 점검 요청

 

실시간 대시보드 네트워크창 상황. 한 번 요청할 때 프리플라이트와 쌍으로 요청됨을 확인할 수 있다.

CORS에서 간단한 요청의 조건을 충족하지 못할 경우, 실제 요청 전에 사전에 허락을 받는 '사전 점검 요청'에 들어간다. 브라우저는 서버에 요청을 하기 전, preflight이라는 HTTP 요청을 먼저 전송한다. 사전 점검을 받고 나서 요청이 가능함을 확인받고 나면 본요청(actual request)을 보낸다.

 

사전 요청을 위한 preflight request 헤더는 이런 것들이 있다.

Origin: 요청 출처
Access-Control-Request-Method: 실제 요청의 메서드
Access-Control-Request-Headers: 실제 요청의 추가 헤더

이렇게 3가지가 프리플라이트 요청 시 헤더에 필요한 것들이다. 이 요청은 어느 출처에서 날아가고 있는지, 내가 이 메서드를 보내도 되는지, 실제 요청에 헤더를 어떤 것을 추가할지에 대한 내용이 들어간다. 실제로 프론트에서 이렇게 코드를 작성할 필요는 없고, 브라우저가 내가 요청한 헤더 또는 메서드를 읽고 자동으로 이렇게 프리플라이트 헤더에 넣어서 요청을 보낸다.

 

preflight 요청 시 method는 "OPTIONS"로 들어간다.

Access-Control-Request-Origin: 서버 측 허가 출처
Access-Control-Request-Methods: 서버 측 허가 메서드
Access-Control-Request-Headers: 서버 측 허가 헤더
Access-Control-Max-Age: 프리플라이트 응답 캐시 시간

요청한 프리플라이트에 대한 응답으로 서버 측에서 헤더에 넣어 보내오는 것들이다. 이 중 프리플라이트 응답 캐시 시간이 무엇인지 잘 몰랐는데, 이렇게 실시간 대시보드의 경우 초 단위로 매출 데이터가 업데이트되는데, 그때마다 두 번씩 요청을 한다는 것은 리소스 낭비이기 때문에 캐싱을 해서 응답을 적당한 시간 동안 스킵할 수 있도록 하는 것이었다. preflight를 처음 접했을 때, 난 OPTIONS 메서드를 보낸 적이 없는데 왜 백엔드 분이 계속 보냈다고 하는지 정말 이해할 수가 없었다. 어쨌든 백엔드 분께서 응답 헤더에 "access-control-allow-methods"에 OPTIONS도 추가해 주셔서 해결되었다. 만약에 이 요청이 거부가 된다고 하면, 실제 요청은 보내지지 않는다.

 

한편, OPTIONS 메서드는 서버나 웹서버의 특정 리소스가 어떤 기능을 지원하는지 클라이언트가 알아볼 수 있도록 해 준다. 

 

2-3. 인증 정보를 포함한 요청


말 그대로 인증 관련 헤더를 포함할 때 사용하는 요청이다. 기본적으로 교차 출처에 대한 요청에는 HTTP나 쿠키 등의 인증에 사용되는 요청 헤더가 자동으로 전송되지 않는다. 인증 관련 헤더를 보내는 경우 클라이언트와 서버 측 모두 약속한 듯이 설정을 해 줘야 한다. 쿠키나 JWT를 클라이언트에서 자동으로 담아서 보내고 싶을 때, 클라이언트에서는 credentials: include  설정을 해 주고, 서버 측에서는 Access-Control-Allow-Credentials: true로 설정을 해 줘야 한다. 이때, 서버에서 해당 설정을 해 주는 순간, Access-Co

ntrol-Allow-Origin:* 은 안된다. 명확한 출처가 있어야 인증정보를 받아올 수 있다. 

프리플라이트 응답 헤더에 인증에 필요한 온갖 값들이 헤더에 담겨 있다.

 

3. 프리플라이트가 필요한 이유


Preflight가 필요한 이유는 CORS문제가 있는지 모르고 있는 서버를 위해서다. 즉 CORS에 관한 아무런 설정이 없는 서버를 위해서다. 먼저 프리플라이트 없는 상황을 가정해 보면, 클라이언트(자바스크립트 코드) 입장에서는 "내 출처는 eunjinii.com이고, 내 정보를 보여줘"하고 브라우저에 보내면, 브라우저는 서버에 요청을 한다. 그런데 서버는 당연히 CORS 설정이 없기 때문에 allow-origin 같은 설정이 없이 그냥 내 정보를 요청한 대로 가지고 온다. 그러고 나서 브라우저는 응답을 확인하고 그제야 클라이언트에게 "이건 교차 출처 리소스야" 하고 에러를 띄운다.

 

프리플라이트 없이 브라우저가 서버에 요청을 날리면, 에러를 냈을 때 이미 서버는 요청을 수행한 상태다.


여기서 문제는 서버가 단순한 get, post 요청이 아니라 delete와 같은 사전 허가 없이 수행해 버리면, 나중에 브라우저가 응답을 받을 때 "이건 교차 출처 리소스야"라고 뒤늦게 이야기해봤자, 서버는 이미 DB에서 해당 내용을 다 지우고 난 상태다. 그렇게 때문에 프리플라이트 요청을 먼저 보내는 거다.

 

프리플라이트 먼저 날리면, 브라우저가 본 요청 날리기 전에 교차출처 에러를 내버린다.


프리플라이트 요청을 먼저 날린다면, 먼저 클라이언트가 브라우저에 요청을 보내면 브라우저 입장에서는 서버의 응답에 allow-origin이 없기 때문에, CORS 에러가 났다고 클라이언트에게 이야기를 해 준다. 이전과 달리, 브라우저가 교차 출처 에러를 낸 시점에 서버는 아무 행동도 하지 않은 상태다. 

 

지금까지 정리한 것처럼, CORS는 동일 출처 정책과 호환성을 유지하면서 안전성을 유지해 교차 출처 접속을 구현한 사양이다. 여러 출처에 걸쳐 리소스를 취급하는 웹을 개발한다면 이 CORS 사양을 기본적으로 이해하는 것부터 시작해야 할 것이다. 

728x90
Comments