최근에 정말 많이 정신 없던 하루였어요.
제가 공부하고 있는 데브코스에서는 VanillaJS
에 대해 다 배우고 나서,
정말 쉴 겨를 없이
CSS
로 딥하게 넘어가는 과도기이기도 했고, Vue
도 상세히 배우고 있고, VanillaJS
데브코스 과제를 하고 글 쓰기 전만 해도 TIL 제대로 못썼다는 죄책감에 사로잡히다,
적고 나니, 저 정말 생각보다 열심히 살았군오... 뿌듯! 😂😂😂
그러다 보니, 저도 노이로제가 잠시 왔었지만,
추석 동안 다시 충전하고 왔답니다!
(이제 밀린 TIL들... 제대로 쓸 거에오!)
그럼 지금부터, 시작해볼까요!
먼저 이 글은, 다음과 같은 분들을 위해 작성되었어요.
CORS
를 처음 마주한 프론트엔드 꿈나무🌈CORS
에 대해 이해하기 힘드신 분들!
만약에 이런 가정을 해봅시다.
짱구네 집이 자동문이라면 어떨까요?!
아무나 짱구네 집에 들어갈 수 있을 겁니다.
그렇지만, 누구도 짱구네 집이 아니라고 말할 수 없죠!
주소는 엄연히 짱구네 집이니까요.
따라서, 브라우저 역시 마찬가지랍니다.
클라이언트는 브라우저라는 자동문을 통해 URL 주소로 누군가가 만들어 놓은 웹 사이트를 이용하죠. 특히 예전에는, 누구나 데이터를 요청하고, 응답할 수 있었어요.
하지만 이러한 시대의 평화는 오래 가지 못했어요. 보안에서의 문제점이 생겼기 때문이죠.
짱구(클라이언트)가 은행 사이트에 놀러갔다고 합시다.
짱구는 워낙 호기심이 많은 터라, 실수로 해커가 만든 웹사이트를 눌렀어요.
그렇다면 어떻게 될까요?
해커가 원하는 요청대로 클라이언트가 요청한 것으로 이루어지는 보안상 문제점이 발생해버립니다.
이를 CSRF 공격이라고 해요!
따라서 브라우저의 고민은 깊어져만 갔어요. 누구나 안전하게 브라우저를 이용할 수 있도록 말이죠!
그래서 브라우저는 한 가지 대안을 찾게 됩니다. 바로 출처에 대한 엄격한 비교였죠. 출처에 대해서 완벽히 같아야 데이터를 응답해주는 보안 정책, 이를 SOP라고 합니다.
그렇다면 우리는 출처에 대해서 알아야겠네요.
사실 출처라는 건 엄연히 브라우저에서 판단해요.
Q: 브라우저마다 기준이 다를 수 있겠네요?!
A: 맞습니다. (ex: IE 브라우저에서는 port를 비교하지 않아요!)
그렇지만, 그래도 어느정도 기준은 비슷합니다.
출처는 다음과 같이 Scheme
, host
, port
를 통해 비교를 합니다.
주의할 건,
port
의 경우 생략되어 보여도,
http
,https
scheme마다port
의 기본값이 있음에 주의합시다!
만약 이 3개가 같다면 같은 출처, 아니라면 다른 출처라는 거죠!
따라서 SOP는 이렇게 요청한 출처와, 응답할 출처가 같은 곳인지를 비교합니다.
쉽게 말하자면, 하도 짱구가 사고를 많이 치니, 브라우저가 짱구 엄마를 고용한 거죠!
아무리 짱구가 사고를 친다 해도, SOP
가 적용된다면, 이제 다른 출처로 인한 공격에 덜 골치 아파지게 됩니다.
비록 짱구는 오늘도 혼나지만 괜찮아요. 적어도 예기치 않은 위험한 공격을 당하지는 않았으니까요!
그렇게 평화롭던 브라우저, 하지만 개발자들은 점차 욕심이 생겼어요.
어떻게, 다른 웹사이트의 유용한 API를 주고받을 수 없을까?!
그도 그럴 것이, API
제공자는 이를 통해 이득을 취할 수 있으며,
사용자는 원하는 데이터를 제공받을 수 있는 것이죠.
따라서 우리의 어ㅡ썸한 개발자들은, 꼼수(?!)를 쓰기 시작해요.
그것이 바로 JSONP
등의 트릭인데요!
결과적으로 이러한 꼼수를 통해 다른 출처임에도 우회하여 사용할 수 있는 거에요.
우리의 브라우저는 다시 고민에 빠지게 됩니다. 그도 그럴 것이 SOP
가 보안 측면에서는 꽤 좋은 대안이었지만
오히려 요청을 제대로 하지 않고 우회하는 방식으로 하는 이상한 문화가 생겨버렸으니까요.
그 꼼수들은 다음 링크에서 볼 수 있읍니다!😆
따라서 CORS
라는 게 생겨난 거에요!
이제는 짱구네 집에 놀러갈 때, 이전까지는 출처가 다르면 보내지를 않았죠!
하지만 이제 훈이가 놀러가도, 짱구네 집에서 훈이가 오는 걸 알고 있다면, 보내주는 거죠! 마치 자동문 앞에 호출기로 확인하는 것과 같죠!
즉, 다른 출처여도, 이미 예상되는 출처라면 서버에서 허용해주는 응답 헤더를 보내, 브라우저가 응답 결과를 보내주는 겁니다.
이를 CORS(Cross Origin Resource Sharing)
이라 해요.
결과적으로 보안은 SOP
보다는 최소화된 보안 정책이지만, 세팅만 잘 해준다면, 그래도 출처가 다르더라도, 이상한 꼼수 없이, 데이터를 주고받도록 브라우저가 새로운 보안 정책을 마련한 거죠!
일단 우리가 반드시 알아야 할 것이 있어요. 그것이 뭐냐면,
⭐
CORS
는 브라우저에서 출처를 비교하고 판단합니다!
브라우저가 비교하면서 결과적으로 동작 방식에 따라
다음과 같이 나누어 요청을 수행해요.
simple requests
(단순 요청)preflighted requests
(사전 요청)
simple requests
(단순 요청)
쉽게 말하자면, 그냥 한 번에 요청과 응답을 주고받는 거에요.
대신, 그만큼 안전성을 보장할 수 있도록, 엄~청 까다롭게 요청 조건을 세팅해놨는데요. MDN에 따르면 다음과 같습니다.
preflighted requests
(사전 요청)simple requests
로 요청을 하지 않았다?! 싶다면 나머지는 바로 사전 요청으로 진행이 돼요.
일단 preflight
라는 말에서 미리 날린다는 느낌이 들죠?!
맞습니다.
마치 미리 놀러가기 전에(본 요청) 놀러가도 되는지 전화를 거는 거죠!(사전 요청)
상세한 과정은 다음 그림과 같습니다.
좀 더 부연 설명해볼까요? MDN
의 예시에 따라서 다음과 같은 요청을 한다면
이렇게 요청과 응답이 나오는데요! 주요 응답 헤더 설명은 다음 그림에 제시되어 있습니다 😆
특이한 건 Max-Age
라는 응답 헤더가 있네요.
이는 아무래도 사전 요청이 2번 이뤄지기 때문에, 서버의 부하를 최소화하기 위해 해당
origin
과header
에 대한 응답을 캐싱해주는 기간을 의미합니다!
이상하게 느껴질 수 있어요. 어찌 보면 2번 요청한다는 건 서버 측에서도 적지 않은 부담이 될 수도 있죠.
하지만, 이는 어쩔 수 없는 선택이기도 해요.
바로 브라우저가 출처를 비교한다는 맹점이 존재하기 때문이죠.
이 세상 서버들이 다 CORS
를 인지하면 좋겠지만, 간혹 CORS
를 모르는 서버들이 있을 수 있어요. 그렇다면 다음과 같은 처리가 발생하게 됩니다.
결과적으로 브라우저는 거부했다고 하지만, 이미 서버는 처리해버리는 엉뚱한 결과가 생겨버리죠.
따라서 모든 서버가 안전하게 요청을 주고받을 수 있도록,
이렇게 2번 요청하는 사전 요청이 필요한 겁니다!
이렇게 동작 방식을 살펴봤는데요, 인증 정보 및 쿠키를 전송하는 지에 대한 여부에 따라 또 CORS
는 인증 정보를 포함한 요청으로도 나뉩니다.
일반적으로 브라우저는 쿠키와 인증 정보에 관해서 매우 민감하다고 생각하기 때문에 함부로 보내주지 않아요.
하지만 보안상 좀 더 빡빡하게 해주었다면, 이를 허용해줍니다.
조건은 다음과 같아요.
credentials: true
를 허용하는 응답 헤더를 설정해주며 credentials
옵션을 넣어주는 경우이죠.이때,
option
은 다음과 같이 3가지가 있답니다.
- omit: 모든 쿠키, 인증 정보 교환 금지!
- same-origin: 동일 출처에 한해서는 허용(기본값)
- include: 포함
이때, 단순 요청 중 GET
메서드는 금지라고 합니다!
이러한 조건들을 통과하면, 비로소 쿠키를 주고받을 수 있는 거죠!
자, 이제 우리는 CORS
의 3가지 종류를 잘 살펴봤습니다.
생각보다 그렇게 어렵지 않습니다!
- 그저 서버가 허용하는 출처를 만족했는지,
- 혹은 옵션과 헤더를 잘 설정해줬는지를 잘 판단해주면 되는 거죠!
자, 그러면 우리는 이제 요청할 때에 있어서는 CORS
방식에 맞춰 문제를 해결하는 방법을 알게 됐어요.
그렇지만 제대로 된 방식으로도 이를 해결하지 못했다면, 다음과 같은 해결 방법을 사용하면 된답니다!
사실 이게 가장 바람직한 방법이에요.
정말 우아한 설정을 통해서 요청을 보냈다 하더라도,
- 출처가 애초부터 허용되지 않도록 설정이 됐다면
- 옵션과 응답 헤더를 깜빡하고 서버 개발자가 세팅해주지 않았다면
결과적으로 브라우저는 클라이언트의 요청이 정상적이라고 판단하지 않겠죠?!
따라서 모든 해결방법에 앞서, 일단 먼저 우리는 서버 개발자와 빠르게 소통해봅시다.
만약 그 서버가 우리 서버라면, 앞으로의 예기치 않은 서버 세팅 문제까지 해결해줄 수 있으니, 일석이조인 셈이죠!
- 개발 환경에 있어서 세팅을 잘 해놓은 상태이고
- 서버의 세팅은 완벽한데
그럼에도 문제가 생긴다면, 개발 환경에서의 프록시 설정도 대안이 될 수 있습니다.
이는 CRA
, Vue-cli
, Webpack-dev-server
등을 통해 세팅을 직접해줄 수 있는데요, 통일되지 않고 각자마다 방법이 달라요!
따라서 공식문서들을 통해 따로 설정해주면 된답니다!
아무래도 모든 것들이 안 된다면, 우리는 프록시 서버를 구축해야 할 거에요.
이것이 가능한 이유는, CORS
는 브라우저에서 판단한다고 했죠?!
따라서 브라우저를 거치지 않은 서버간 요청은 CORS
를 따지지 않는 것이죠.
좀 더 이해하기 쉽게 말하자면, 훈이가 짱구와 연락할 수 없는 상황일 때, 흰둥이를 통해 짱구와 연락을 주고 받는 거에요!
따라서 이렇게 서버를 구축하면 가능할 수는 있겠지만, 문제는
1. 추가로 서버를 세팅해야 한다는 한계점과
2. 이로 인한 시간적, 인적 자원의 소요
가 있겠어요. 따라서 만약 프록시 서버가 이미 구축되어 있지 않다면,
이를 위해서는 사전에 해결할 수 없는지를 미리 고민해봐야겠군요!
결과적으로 CORS에 있어서 다음 사항만은 꼭 유의했으면 좋겠어요.
CORS
는 완벽하지 않은, 최소한의 보안 정책이므로, 정말 잘 세팅하는 데 주의를 기울이기를 바라요!CORS
의 비교 주체는 브라우저라는 점을 꼭 명심해줬으면 좋겠어요! 이에 대한 조건 충족 여부에 따라 단순 요청, 사전 요청 등을 자동으로 수행하기 때문이죠!CORS
가 나왔다고 망설이지 말아주세요 😆아무래도 저와 같은 신입 개발자들에겐
CORS
란 꽤나 끔찍한 일이지만,
이 글이 누군가에게는 도움이 됐으면 좋겠어요! 이상 🌈
MDN - SOP
JSONP, form 등을 통한 우회방식에 대하여
MDN - Origin
MDN - CORS
MDN - requests with credentials
Blog - CORS는 왜 우리를 힘들게 하는 걸까?
Blog - CORS, 왜 모르는가?
저도 최근에 CORS에 대해서 포스팅 했었는데 저보다 훨씬 보기 좋게 잘쓰셨네요!!
잘 보고 갑니다~!