CORS를 처음 마주하는 분들에게

young_pallete·2021년 9월 21일
169

Web Browser

목록 보기
3/3

시작하며🌈

최근에 정말 많이 정신 없던 하루였어요.
제가 공부하고 있는 데브코스에서는 VanillaJS에 대해 다 배우고 나서,
정말 쉴 겨를 없이

  • 바로 CSS로 딥하게 넘어가는 과도기이기도 했고,
  • 현재는 Vue도 상세히 배우고 있고,
  • 프로젝트는 따로 유지보수를 하면서,
  • VanillaJS 데브코스 과제를 하고
  • 주 3일 알고리즘 스터디까지 하면서
  • 다가올 면접에 대한 압박감 속에서 면접 준비와
  • 다른 기업 과제 테스트 제출과
  • 이번에 운이 좋게 데브코스 과정에서 우리 팀원들의 지지 덕에,
    발표 영상을 촬영하고 왔네유 😆

글 쓰기 전만 해도 TIL 제대로 못썼다는 죄책감에 사로잡히다,
적고 나니, 저 정말 생각보다 열심히 살았군오... 뿌듯! 😂😂😂

그러다 보니, 저도 노이로제가 잠시 왔었지만,
추석 동안 다시 충전하고 왔답니다!
(이제 밀린 TIL들... 제대로 쓸 거에오!)

그럼 지금부터, 시작해볼까요!

먼저 이 글은, 다음과 같은 분들을 위해 작성되었어요.

  • CORS를 처음 마주한 프론트엔드 꿈나무🌈
  • CORS에 대해 이해하기 힘드신 분들!

본론 📖

CORS의 배경과 필요성

CORS가 없던 과거

만약에 이런 가정을 해봅시다.
짱구네 집이 자동문이라면 어떨까요?!

아무나 짱구네 집에 들어갈 수 있을 겁니다.
그렇지만, 누구도 짱구네 집이 아니라고 말할 수 없죠!
주소는 엄연히 짱구네 집이니까요.


따라서, 브라우저 역시 마찬가지랍니다.
클라이언트는 브라우저라는 자동문을 통해 URL 주소로 누군가가 만들어 놓은 웹 사이트를 이용하죠. 특히 예전에는, 누구나 데이터를 요청하고, 응답할 수 있었어요.

SOP의 탄생

하지만 이러한 시대의 평화는 오래 가지 못했어요. 보안에서의 문제점이 생겼기 때문이죠.

짱구(클라이언트)가 은행 사이트에 놀러갔다고 합시다.
짱구는 워낙 호기심이 많은 터라, 실수로 해커가 만든 웹사이트를 눌렀어요.

그렇다면 어떻게 될까요?
해커가 원하는 요청대로 클라이언트가 요청한 것으로 이루어지는 보안상 문제점이 발생해버립니다.

이를 CSRF 공격이라고 해요!

따라서 브라우저의 고민은 깊어져만 갔어요. 누구나 안전하게 브라우저를 이용할 수 있도록 말이죠!
그래서 브라우저는 한 가지 대안을 찾게 됩니다. 바로 출처에 대한 엄격한 비교였죠. 출처에 대해서 완벽히 같아야 데이터를 응답해주는 보안 정책, 이를 SOP라고 합니다.

출처란?

그렇다면 우리는 출처에 대해서 알아야겠네요.
사실 출처라는 건 엄연히 브라우저에서 판단해요.

Q: 브라우저마다 기준이 다를 수 있겠네요?!

A: 맞습니다. (ex: IE 브라우저에서는 port를 비교하지 않아요!)

그렇지만, 그래도 어느정도 기준은 비슷합니다.
출처는 다음과 같이 Scheme, host, port를 통해 비교를 합니다.

주의할 건, port의 경우 생략되어 보여도,
http, https scheme마다 port의 기본값이 있음에 주의합시다!

만약 이 3개가 같다면 같은 출처, 아니라면 다른 출처라는 거죠!

SOP

따라서 SOP는 이렇게 요청한 출처와, 응답할 출처가 같은 곳인지를 비교합니다.

쉽게 말하자면, 하도 짱구가 사고를 많이 치니, 브라우저가 짱구 엄마를 고용한 거죠!

아무리 짱구가 사고를 친다 해도, SOP가 적용된다면, 이제 다른 출처로 인한 공격에 덜 골치 아파지게 됩니다.

비록 짱구는 오늘도 혼나지만 괜찮아요. 적어도 예기치 않은 위험한 공격을 당하지는 않았으니까요!

SOP의 한계

그렇게 평화롭던 브라우저, 하지만 개발자들은 점차 욕심이 생겼어요.

어떻게, 다른 웹사이트의 유용한 API를 주고받을 수 없을까?!

그도 그럴 것이, API 제공자는 이를 통해 이득을 취할 수 있으며,
사용자는 원하는 데이터를 제공받을 수 있는 것이죠.

따라서 우리의 어ㅡ썸한 개발자들은, 꼼수(?!)를 쓰기 시작해요.
그것이 바로 JSONP 등의 트릭인데요!
결과적으로 이러한 꼼수를 통해 다른 출처임에도 우회하여 사용할 수 있는 거에요.

우리의 브라우저는 다시 고민에 빠지게 됩니다. 그도 그럴 것이 SOP가 보안 측면에서는 꽤 좋은 대안이었지만

오히려 요청을 제대로 하지 않고 우회하는 방식으로 하는 이상한 문화가 생겨버렸으니까요.
그 꼼수들은 다음 링크에서 볼 수 있읍니다!😆

따라서 CORS라는 게 생겨난 거에요!

CORS란?!

이제는 짱구네 집에 놀러갈 때, 이전까지는 출처가 다르면 보내지를 않았죠!
하지만 이제 훈이가 놀러가도, 짱구네 집에서 훈이가 오는 걸 알고 있다면, 보내주는 거죠! 마치 자동문 앞에 호출기로 확인하는 것과 같죠!

즉, 다른 출처여도, 이미 예상되는 출처라면 서버에서 허용해주는 응답 헤더를 보내, 브라우저가 응답 결과를 보내주는 겁니다.
이를 CORS(Cross Origin Resource Sharing)이라 해요.

결과적으로 보안은 SOP보다는 최소화된 보안 정책이지만, 세팅만 잘 해준다면, 그래도 출처가 다르더라도, 이상한 꼼수 없이, 데이터를 주고받도록 브라우저가 새로운 보안 정책을 마련한 거죠!

CORS의 작동 방식

일단 우리가 반드시 알아야 할 것이 있어요. 그것이 뭐냐면,

CORS는 브라우저에서 출처를 비교하고 판단합니다!

브라우저가 비교하면서 결과적으로 동작 방식에 따라
다음과 같이 나누어 요청을 수행해요.

  • simple requests (단순 요청)
  • preflighted requests (사전 요청)

simple requests (단순 요청)


쉽게 말하자면, 그냥 한 번에 요청과 응답을 주고받는 거에요.
대신, 그만큼 안전성을 보장할 수 있도록, 엄~청 까다롭게 요청 조건을 세팅해놨는데요. MDN에 따르면 다음과 같습니다.

preflighted requests (사전 요청)

simple requests로 요청을 하지 않았다?! 싶다면 나머지는 바로 사전 요청으로 진행이 돼요.
일단 preflight라는 말에서 미리 날린다는 느낌이 들죠?!
맞습니다.

  • 미리 한 번 찔러보고,
  • 다음에 본 요청을 수행하는 방식으로 2번 요청하는데요.

마치 미리 놀러가기 전에(본 요청) 놀러가도 되는지 전화를 거는 거죠!(사전 요청)

상세한 과정은 다음 그림과 같습니다.

좀 더 부연 설명해볼까요? MDN의 예시에 따라서 다음과 같은 요청을 한다면

이렇게 요청과 응답이 나오는데요! 주요 응답 헤더 설명은 다음 그림에 제시되어 있습니다 😆
특이한 건 Max-Age라는 응답 헤더가 있네요.

이는 아무래도 사전 요청이 2번 이뤄지기 때문에, 서버의 부하를 최소화하기 위해 해당 originheader에 대한 응답을 캐싱해주는 기간을 의미합니다!

잠깐, 이상해요! 왜 두 번 요청하죠?!

이상하게 느껴질 수 있어요. 어찌 보면 2번 요청한다는 건 서버 측에서도 적지 않은 부담이 될 수도 있죠.
하지만, 이는 어쩔 수 없는 선택이기도 해요.

바로 브라우저가 출처를 비교한다는 맹점이 존재하기 때문이죠.

이 세상 서버들이 다 CORS를 인지하면 좋겠지만, 간혹 CORS를 모르는 서버들이 있을 수 있어요. 그렇다면 다음과 같은 처리가 발생하게 됩니다.

결과적으로 브라우저는 거부했다고 하지만, 이미 서버는 처리해버리는 엉뚱한 결과가 생겨버리죠.

따라서 모든 서버가 안전하게 요청을 주고받을 수 있도록,
이렇게 2번 요청하는 사전 요청이 필요한 겁니다!


credentials에 따른 분류.

이렇게 동작 방식을 살펴봤는데요, 인증 정보 및 쿠키를 전송하는 지에 대한 여부에 따라 또 CORS는 인증 정보를 포함한 요청으로도 나뉩니다.

일반적으로 브라우저는 쿠키와 인증 정보에 관해서 매우 민감하다고 생각하기 때문에 함부로 보내주지 않아요.
하지만 보안상 좀 더 빡빡하게 해주었다면, 이를 허용해줍니다.

조건은 다음과 같아요.

  • 서버에서는 허용하는 출처를 "*"가 아닌 직접 명시를 해주며
  • credentials: true를 허용하는 응답 헤더를 설정해주며
  • 클라이언트의 경우 credentials 옵션을 넣어주는 경우이죠.

    이때, option은 다음과 같이 3가지가 있답니다.

    • omit: 모든 쿠키, 인증 정보 교환 금지!
    • same-origin: 동일 출처에 한해서는 허용(기본값)
    • include: 포함

이때, 단순 요청 중 GET 메서드는 금지라고 합니다!
이러한 조건들을 통과하면, 비로소 쿠키를 주고받을 수 있는 거죠!

자, 이제 우리는 CORS의 3가지 종류를 잘 살펴봤습니다.

생각보다 그렇게 어렵지 않습니다!

  • 그저 서버가 허용하는 출처를 만족했는지,
  • 혹은 옵션과 헤더를 잘 설정해줬는지를 잘 판단해주면 되는 거죠!

자, 그러면 우리는 이제 요청할 때에 있어서는 CORS 방식에 맞춰 문제를 해결하는 방법을 알게 됐어요.
그렇지만 제대로 된 방식으로도 이를 해결하지 못했다면, 다음과 같은 해결 방법을 사용하면 된답니다!


문제 해결 방법

1. 서버 개발자와 빠르게 소통한다!

사실 이게 가장 바람직한 방법이에요.
정말 우아한 설정을 통해서 요청을 보냈다 하더라도,

  • 출처가 애초부터 허용되지 않도록 설정이 됐다면
  • 옵션과 응답 헤더를 깜빡하고 서버 개발자가 세팅해주지 않았다면

결과적으로 브라우저는 클라이언트의 요청이 정상적이라고 판단하지 않겠죠?!

따라서 모든 해결방법에 앞서, 일단 먼저 우리는 서버 개발자와 빠르게 소통해봅시다.

만약 그 서버가 우리 서버라면, 앞으로의 예기치 않은 서버 세팅 문제까지 해결해줄 수 있으니, 일석이조인 셈이죠!

2. 개발 환경 프록시 설정

  • 개발 환경에 있어서 세팅을 잘 해놓은 상태이고
  • 서버의 세팅은 완벽한데

그럼에도 문제가 생긴다면, 개발 환경에서의 프록시 설정도 대안이 될 수 있습니다.

이는 CRA, Vue-cli, Webpack-dev-server 등을 통해 세팅을 직접해줄 수 있는데요, 통일되지 않고 각자마다 방법이 달라요!

따라서 공식문서들을 통해 따로 설정해주면 된답니다!

3. 프록시 서버 구축

아무래도 모든 것들이 안 된다면, 우리는 프록시 서버를 구축해야 할 거에요.
이것이 가능한 이유는, CORS는 브라우저에서 판단한다고 했죠?!
따라서 브라우저를 거치지 않은 서버간 요청은 CORS를 따지지 않는 것이죠.

좀 더 이해하기 쉽게 말하자면, 훈이가 짱구와 연락할 수 없는 상황일 때, 흰둥이를 통해 짱구와 연락을 주고 받는 거에요!

따라서 이렇게 서버를 구축하면 가능할 수는 있겠지만, 문제는

1. 추가로 서버를 세팅해야 한다는 한계점과
2. 이로 인한 시간적, 인적 자원의 소요

가 있겠어요. 따라서 만약 프록시 서버가 이미 구축되어 있지 않다면,
이를 위해서는 사전에 해결할 수 없는지를 미리 고민해봐야겠군요!


정리

결과적으로 CORS에 있어서 다음 사항만은 꼭 유의했으면 좋겠어요.

  • CORS는 완벽하지 않은, 최소한의 보안 정책이므로, 정말 잘 세팅하는 데 주의를 기울이기를 바라요!
  • CORS의 비교 주체는 브라우저라는 점을 꼭 명심해줬으면 좋겠어요! 이에 대한 조건 충족 여부에 따라 단순 요청, 사전 요청 등을 자동으로 수행하기 때문이죠!
  • 일단 소통이 가능한 환경이라면 해당 서버 개발자와의 협업을 통해 문제를 해결하는 것이 가장 바람직하다는 것!
    CORS가 나왔다고 망설이지 말아주세요 😆

아무래도 저와 같은 신입 개발자들에겐 CORS란 꽤나 끔찍한 일이지만,
이 글이 누군가에게는 도움이 됐으면 좋겠어요! 이상 🌈


참고자료 📃

SOP에 대하여

MDN - SOP
JSONP, form 등을 통한 우회방식에 대하여

CORS에 대하여

MDN - Origin
MDN - CORS
MDN - requests with credentials
Blog - CORS는 왜 우리를 힘들게 하는 걸까?
Blog - CORS, 왜 모르는가?

문제 해결 방식에 대하여

프록시 서버에 대한 글
Blog - CORS에 대한 몇 가지 오해

profile
People are scared of falling to the bottom but born from there. What they've lost is nth. 😉

17개의 댓글

comment-user-thumbnail
2021년 9월 24일

저도 최근에 CORS에 대해서 포스팅 했었는데 저보다 훨씬 보기 좋게 잘쓰셨네요!!
잘 보고 갑니다~!

1개의 답글
comment-user-thumbnail
2021년 9월 24일

CORS 때문에 고민이 많았는데, 너무 잘 정리해주셔서 이해하는데 도움이 되었습니다.
좋은 글 감사합니다

1개의 답글
comment-user-thumbnail
2021년 9월 25일

CORS때문에 고통받았는데.. 감사합니다~~

1개의 답글
comment-user-thumbnail
2021년 9월 29일

엄청난 정성글.. 이해하기 쉽게 설명하신 부분에서 참 감격스럽네요!! 재미있게 잘 보고 갑니다!

1개의 답글
comment-user-thumbnail
2021년 9월 29일

cors 관련 오류가 많이났는데 감사합니다...!

1개의 답글
comment-user-thumbnail
2021년 9월 30일

많이 배워갑니다. 감사합니다.!

1개의 답글
comment-user-thumbnail
2021년 10월 28일

도움이 많이 됐어요! 감사합니다 :)

답글 달기
comment-user-thumbnail
2021년 11월 3일

짱구 좋아하는데 짱구로 설명해주셔서 정말 이해가 쏙쏙 잘 됐어요! 감사합니다

답글 달기
comment-user-thumbnail
2022년 7월 30일

와 대박,, 너무 깔끔하게 정리하셨네요,,
처음 들어보는 개념인데도 한 번에 이해가 갔습니다,,
개인 블로그에 정리해도 될까요?

1개의 답글
comment-user-thumbnail
2023년 4월 28일

와 정리 진짜 대박이네요. 감사합니다

답글 달기