CORS를 어떻게 알게 되었는가?
이번에 실제 서비스를 배포하는 기업과 함께 진행되는 프로젝트에서 CORS 에러를 처음 만나게 되었다
사용자가 하나의 콘텐츠를 결제하면, 동영상 강의와 그 외 수업 노트 및 다른 사용자와 함께 댓글들로 소통할 수 있는 페이지를 담당했다. 그리고 이 페이지는 결제를 한 사람에 한해, 보여질 수 있는 페이지였다. API를 호출해서 동영상을 가져오려고 시도했지만, 이때 처음으로 CORS에러를 만나며 영상 호출에 실패했다.
이때 처음으로 CORS라는 아주 복잡스러워 보이는 친구를 만나, 거의 아무것도 진행할 수 없었던 경험이 있었기에... 이번을 기회로 CORS에 대해 제대로 알고 넘어갈 수 있도록 해보고자 한다!
"교차 출처 리소스 공유" 라고 하는데,(사실 한국말이 크게 와닿지는 않았다.. 뭔가 어려운 말로 이뤄진 것 같은 느낌쓰) 최대한 스스로 이해하기 쉬운 말로 바꿔서 적어보려고 한다.
Cross Origin Resource Sharing
한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제.
=> CORS에 대한 정의와, 실제로 만났던 문제가 뭔가 따로 노는 느낌이 있어서 최대한 쉬운 말로 생각을 해보려고 했다.
Cross와 Sharing에서 느껴지듯이 말 그대로, 다른 출처간의 리소스를 서로 간에 공유한다는 의미이다.
더 쉽게 말해서 로그인을 한 어느 웹 서버가 있는데, 이때 로그인을 한 곳에서 다른 출처의 데이터를 주고 받는 과정을 말한다.
이 말도 어렵고 와닿지 않아서 쉽게 설명해준 예제를 찾아 다녔다..
만약, 로그인을 한 벨로그에서 네이버 지도 api의 데이터를 받으려면 다른 출처로의 데이터를 공유한다는 의미가 되는데 데이터 공유를 허락해주는 것이 CORS이다.
작업을 진행하는데, CORS 에러는 정말 정말 주적이었다.. 이 친구는 왜 나를 막을까..? 이거저거 시도해보면서, 얼른 프로젝트를 진행하고 앞으론 나아가고 싶었는데 CORS에 가로 막혀 아예 전진조차 할 수 없는 상태에 이르렀고, 쉽게 해결이 되지 않아 일단 다른 부분들을 해결했고 영상이 들어왔을 때의 상황들을 보기 위해 일단 임의의 url을 영상의 소스로 활용했다(일단 1패요..)
그러다, 문득 그런 생각이 들었다.
"CORS 너 근데 왜 있는 거야?? 웹개발자들을 괴롭히려고 있는 건 아닌데... 분명히 이렇게 나의 console에 새빨갛게 존재하는 이유가 있을텐데.."
우리가 한 웹사이트에 로그인을 하게 되면 로그인한 토큰이 쿠키나 스토리지에 저장된다. 즉, 우리의 로그인 정보가 남아있다는 의미이다.
이때 로그인을 한 정보는 절대절대절대적으로 잘 지켜져야 한다.
하지만 우리가 다른 api, 다른 출처로 데이터를 요청 했을 때 그 출처가 나쁜 마음을 품는다면 그 출처가 쿠키나 스토리지에 저장되어 있는 나의 로그인 정보를 빼갈 수 있다는 의미가 된다.
쉽게 말해서,
로그인 -> 쿠키나 스토리지에 로그인 인증 토큰이 저장됨 -> 나쁜 마음을 먹은 다른 출처에 데이터 요청을 보냄 -> 그 출처에서 작업된 html이나 javascript(example입니다...!)의 코드(이 코드가 나의 정보를 빼가도록 설계가 되어 있다면)에 의해 나의 인증정보가 악용될 수 있음.
그래서 이때, 다른 출처로의 리소스 공유를 허락할 것이냐!! 라고 물어 보는 것이 CORS 에러였던 것이다.
그리고 "어떠한 정보가 있거나 조건을 충족" 하면 다른 출처와 리소스를 공유할 수 있도록 허락해주는 것이 CORS이다.
CORS의 반대 개념으로 SOP가 있는데, Same-Origin Policy로 "동일 출처 정책"이다.
위에 설명에 따르면 뭔가... CORS가 다른 출처 간의 공유를 막고 있는 것으로 보이지만 CORS는 다른 출처 간의 리소스 공유를 허락해주는 역할이고 실제로 막는 역할을 하는 것이 SOP이다.
같은 출처(ex. 같은 url) 간에만 리소스 공유를 허락한다는 의미이다.
정리하자면,
1. 기본적으로 다른 웹사이트나 api에 데이터를 요청하고 주고 받는 것은 막혀 있다.
2. 이것을 막는 것이 "SOP"라는 방식이다. 즉, 같은 출처끼리만 데이터를 주고 받는 것이 가능하도록 하는 방식이다.하지만, 웹사이트가 점점 더 다양해지면서 여러 서버나 출처 간에 더이터를 주고 받는 것이 더욱 자유로워졌다.
- 다른 출처 간의 데이터 공유를 가능하게 해주는 것이 CORS이다.
요청을 받는 백엔드에서 공유를 가능하게 하는 사이트나 출처 등을 명시해주면 된다. 명시되어 있는 요청을 보내면 CORS가 허용이 되어 에러 없이 정상적으로 요청을 보낼 수 있다.
만약에 반대로, 로그인이 아닌 그냥 아무나 보낼 수 있도록 요청을 설정하려면 출처들을 명시해두는 곳에 별표(*), 와일드카드를 적어서 보내면 된다고 한다.
https://123456.com:345
scheme = https
domain = 123456
port = 345
네이버 지도 API는 답장의 header에 Access-Control-Allow-Origin 정보를 담아 보낸다. 이때 네이버 지도 api에 123456.com이 등록 되어 있다면, Access-Control-Allow-Origin에 123456의 url이 담겨져 있을 것이다.
그러면 브라우저(크롬 등)가 요청과 답장을 비교해서 Origin에 담겨 있는 출처값이 api의 응답에 있는 Access-control-allow-origin에 출처값과 동일하면 안전한 요청으로 보고 CORS가 허용된다.
반대로 없으면 우리가 맨날 마주하는 에러가 나타난다.
로그인과 같이 사용자 식별정도인 token이 담겨 있으면 얘기가 조금 달라진다.
위의 과정과 전체적으로 비슷하지만 조금 더 엄격한 기준이 제시된다.
header에 추가되는 부분
1. 요청을 보내는 쪽인 로그인한 벨로그에서 요청의 옵션에 withCredentials:true을 추가해서 보낸다.
2. 받는 쪽에서도 별표나 와일드카드로 모두 받을 수 있도록 지정하는 것이 아닌 요청을 보낸 쪽의(로그인한 벨로그) 출처나 웹페이지 주소를 정확히 명시한 다음 Access-Control-Allow-Origin 항목을 true로 맞춰야 한다.
이렇게 요청을 보내고 받다 보면 자연스럽게 개발자도구의 network탭과 친해진다.
network를 자주 보다 보니 서버와 데이터를 주고 받을 때, 종종 preFlight를 본적이 있다. 있는 것도 있고 없는 것도 있어서 이게 뭘까 궁금했는데,
알아보니 요청을 보내는 것도 3개로 나뉜다고 한다.
- Simple requset
get, post에서 사용되는 무난하게 사용되는 요청으로 기본값 같은 느낌이다.- Preflight request
delete나 put은 본 요청 전에 preflight와 같은 요청을 먼저 보내서 본 요청이 안전한지 먼저 확인한다.
왜요? 굳이 한 번더 확인하는 이유가 있을까요?
서버에 영향을 주는 요청이기 때문이다. (delete의 경우, 서버에 저장되어 있는 데이터를 지우기 때문에 영향을 준다.)****- Credentialed Request
쿠키나 jwt 토큰을 담아 보낼 때, 서버에 withCredential : true로 보내는 것이다.