본문 바로가기
Develop/백엔드 & 서버

CORS 이슈 해결하기

by 구운밤이다 2021. 1. 18.
728x90
반응형

cors는 cross origin resource sharing의 약자로 개발할 때 많이 발생하는 이슈 중 하나입니다. 특히 최근에는 서버와 프론트의 ip 주소가 달라 이런 경우가 더 많이 발생하고 있는데, 이 이슈는 브라우저에서 요청 못하게 막는 거여서 제대로 해결하려면 어려운 부분이죠. cors가 뭔지 먼저 알아봅시다.

CORS의 의미

cors에서 말하는 origin은 url에서 프로토콜(https) + 호스트(www.naver.com) + path(/users) + 쿼리(?sort=asc&page=1) + fragment(#foo)로 이루어진 부분을 말한다. 여기에 포트번호가 있다면 포트번호까지 합친 것이 origin 이고 포트번호가 없다면 기본포트인 80을 사용한다는 의미입니다.

여기서 포트번호까지 같아야 같은 출처(Origin)라고 인정합니다. cors에 반대되는 정책 sop(same-origin policy)에서는 같은출처에서만 리소스를 공유하고 있습니다. 즉 cors 자체는 다른 출처(cross origin)에서 리소스를 사용할 수 있게 해주는 정책인 것으로 나쁜 게 아니었던 거죠. 이 정책을 잘 안 지켜서 개발해 이슈가 발생했던 건데, 다른 출처에서 앱이 통신하는데 제약을 두지 않으면 CSRF(cross site request forgery)나 XSS(cross site scripting)등 으로 정보를 쉽게 탈취할 수 있게 됩니다.

브라우저는 url 구성요소중 scheme(http://), host, port 3가지 동일하면 같은 origin이라고 판별합니다. 브라우저를 통하지 않고 서버 간 통신할 때는 cors 정책이 적용되지 않습니다. 프론트에서 cors 에러 나도 서버에는 정상 로그가 남기 때문에 어떻게 동작하는지 알아야하는 것이죠.🥲

CORS의 동작방식

브라우저는 요청을 보낼 때 req header에 origin 필드를 담아 보냅니다. 서버가 이 요청에 응답할 때에는 Access-Control-Allow-Origin 값에 이 리소스를 접근하는 것이 허용된 출처를 내려주는데, 응답을 받은 브라우저는 보냈던 req의 origin과 서버가 보내준 Access-Control-Allow-Origin 을 비교해본 후 응답이 유효한지 아닌지 결정하게 됩니다. 여기에는 3가지 시나리오 존재하는데요.

1. Preflight Request

가장 일반적인 시나리오입니다. 크롬에서 개발자 도구로 네트워크 탭을 자주 보셨다면 이 형태를 이용하고 있는 것을 아마 보셨을텐데요. 본 요청 보내기 전에 보내는 예비 요청을 http options 메소드를 이용해 보내게 됩니다. 브라우저가 스스로 이 요청을 보내는 것이 안전한지 확인하는 거죠. 예를 들어 자바스크립트로 fetch api 를 사용하면 브라우저는 예비요청을 먼저 보내 서버가 어떤 것을 허용하고 금지하는지 응답 헤더에 담아 알려줍니다. 브라우저는 이 서버가 알려준 정책과 자신이 보낸 예비요청과 비교한 뒤, 안전하면 같은 엔드포인트로 다시 본 요청을 보냅니다. 서버가 응답하면 그제서야 이 응답 데이터를 자바스크립트에게 넘겨주고 우리는 데이터를 받아 개발을 할 수 있는 거죠.

2. Simple Request

이 요청은 예비요청을 생략하는 방식입니다. 본 요청을 바로 보내고, 응답 헤더에 Access-Control-Allow-Origin 값 보내주면 그때 CORS 정책을 위반했는지 검사합니다. 1과 로직은 같지만 예비요청을 하지 않는다는 것이 차이점입니다. 아래 조건일때 Simple Req를 사용할 수 있습니다. (예비요청 생략 가능 조건)

  • Get, Post, Head 메소드일때
  • Accept, Accept-Language, Accept, Accept-Language, Content-Language, Content-Type, DPR, Downlink, Save-Data, Viewport-Width, Width를 제외한 헤더를 사용하면 안된다.
  • 만약 Content-Type를 사용하는 경우에는 application/x-www-form-urlencoded, multipart/form-data, text/plain만 허용된다.

3. Credentialed Req

좀 더 보안 강화된 버전입니다. credential 옵션을 사용하는 시나리오인데, 이 옵션에는 3가지 값이 잇습니다.

  • same-origin(같은 출처간 요청만 인증정보 담기 가능)
  • include(모든 요청에 인증정보 가능)
  • omit(모든 요청에 불가능)

앞 2개 옵션은 좀 더 보안이 강합니다. include 값을 골랐다면 Access-Control-Allow-Origin에 * 사용하면 안됩니다. 응답 헤더에는 Access-Control-Allow Credentials: true 가 존재해야 한다.

이렇게 cors에 대해 파헤쳐 봤으니 해결방법을 공부해봅시다. 이렇게 공부해야 나중에 뭐 설명도 잘해줄 수 있고, 날리는 시간이 적겠죠..?

CORS 해결 방법

정석은 서버에서 Access-Control-Allow-Origin에 프론트 url을 넣어주는 것입니다. 당장 편하자고 Access-Control-Allow-Origin: *으로 세팅하면 보안적으로 심각한 이슈가 발생할 수도 있습니다. 위에 얘기했던 XSS, CSRF 공격등을 받을 수 있는 거죠.

Nginx, 아파치 같은 서버 엔진에서 이 응답 헤더에 Access-Control-Allow-Origin 값을 추가할 수 있지만, 복잡한 세팅으로 어려운 경우가 대부분입니다. => 소스코드 내 응답 미들웨어 등을 사용하세요. Spring, Express, Django 등 대부분의 프레임워크에서 사용가능한 라이브러리가 존재합니다!

두번째로 개발할때에는 Webpack Dev Server 로 리버스 프록싱하는 방법이 있습니다. CORS 정책을 지킨 것처럼 브라우저를 속이는데, 서버에 요청을 보낼때 프록시를 사용해 브라우저는 로컬 서버 url로 요청을 보내는 것으로 속이고, 뒤에서 웹팩이 실제 서버주소로 프록싱해주어 cors를 쉽게 우회하는 방법입니다. 이는 http-proxy-middleware 등의 라이브러리를 사용하면 쉽게 구축할 수 있습니다. 하지만 실제 프로덕션 환경으로 빌드해서 올리면 webpack-dev-server 가 구동해주는 환경이 아니므로 에러가 나게됩니다.ㅎㅎㅎ

서버에서 응답 보낼 때 ACAO 에 프론트 주소 넣어주는 것이 정답!


참고 - CORS는 왜 이렇게 우리를 힘들게 하는걸까?

728x90
반응형

댓글