한 사이트에서 주소가 다른 서버로 요청을 보낼 때 자주 접하게 되는 오류이다.
주소가 aaa.com인 웹사이트에서 URL이 bbb.com인 서비스에 API로 정보를 받아오기 위해 프론트에서 HTTP 요청을 보냈을 때 미리 어떤 설정을 해 주지 않으면 CORS 문제로 막히게 된다.
Postman이나 백엔드에서 HTTP 요청을 보내면 잘 되는데 왜 프론트에서 보내면 안될까?
CORS라는 이유로 요청을 막는건 서비스가 아니라 브라우저이다. 즉, 네이버 지도 같은 API 제공 서비스들이 사용자의 브라우저를 신뢰하지 않는 것이 아니라 브라우저가 사용자가 방문한 사이트를 신뢰하지 않는 것이다.
보통 사용자가 방문하는 사이트들은 사용자가 직접 개발한 사이트가 아니다.
예시를 보자,
'착한 닷컴'이라는 사이트가 있고 사용자도 가입 후 잘 사용하고 있다고 가정하자. 우리는 보통 어떤 사이트에 로그인을 하면 다음에 접속했을 때 아이디, 비밀번호를 다시 입력할 필요가 없도록 로그인이 유지되고 있는 경우가 많다. (토큰, 쿠키, 세션) 즉, 인증에 관한 정보가 브라우저에 저장되어 있다는 말이다.
그런데 이때 누군가가 '착한 닷컴'으로부터 사용자의 정보를 빼내기 위해 '나쁜 닷컴'을 만들었다. 이 사람들은 링크가 담긴 메일 등을 통해서 '나쁜 닷컴'에 접속하도록 유도한다. 사용자가 '나쁜 닷컴'에 접속하게 되면 그들이 만든 HTML, CSS, Javascript 코드가 브라우저에 저장된다.
'착한 닷컴'에서 사용자의 개인정보를 조회하는 요청에 크롬에 저장된 사용자의 '착한 닷컴' 토큰을 이용해서 탈취한 정보를 '나쁜 닷컴'의 서버로 보낼 수 있다는 말이다.
브라우저는 위와 같은 상황을 막기 위해 어떤 사이트에서 다른 사이트로는 요청이 못가도록 방지하고 있다.
동일한 출처(URL)끼리만 API등의 데이터 접근이 가능하도록 요청을 막는 것을 SOP (Same-Origin Policy, 동일 출처 정책)라고 하고, 요청을 허용하는 것을 CORS 라고 한다.
SOP의 반대 개념이다. 다른 출처간에 리소스를 공유할 수 있도록 하는 것을 말한다.
출처는 보내고 받는 각각의 위치 (웹사이트 주소, API주소), 리소스는 주고 받아지는 데이터라고 보면 된다.
브라우저에서 서로 다른 출처끼리 요청을 주고받는 것은 애초에 금지되어 있었다.
하지만 웹 생태계가 다양해지면서 여러 서비스들간에 보다 자유롭게 데이터를 주고받아질 필요가 생겼다. 개발자들은 초기에 JSONP등의 방식으로 우회하는 방법을 사용했다. 이후 합의된 출처들간에 합법적으로 허용해주기 위해 어떤 기준을 충족시키면 리소스 공유가 되도록 만들어진 메커니즘이 교차 출처 자원 공유 방식, CORS이다.
그렇다면 어떤 기준이라는게 무엇일까?
요청을 받는 백엔드에서 허용할 다른 출처들을 미리 명시해 두는 것이다. 백엔드 프레임워크(Spring, Django, Express 등 ...) 문서를 살펴보면 CORS 옵션을 넣는 방법들이 쉽게 설명되어 있다.
네이버 지도 API 등의 서비스들도 콘솔에 들어가보면 CORS를 허용해줄 주소들을 지정하는 페이지가 있다.
aaa.com에서 네이버 지도 API로 요청을 보낸다. 이는 다른 출처로의 요청이니까 cross-origin 요청이다. 브라우저는 이처럼 다른 출처끼리의 요청이 보내질 때 요청에 Origin이라는 header를 추가한다. Origin에는 요청하는 쪽의 scheme과 도메인, 그리고 포트가 담긴다.
https://aaa.com:443 의 경우
이러한 요청을 받은 네이버 지도 API 서버는 응답 헤더에 지정된 Access-Control-Allow-Origin 정보를 실어서 보낸다. 만약 aaa.com이 등록된 상태면 aaa.com도 포함되어 있을 것이다.
브라우저는 Origin에서 보낸 출처값과 서버의 응답 헤더에 담긴 Access-Control-Allow-Origin에 포함되어 있으면 안전한 요청으로 간주하고 응답 데이터를 받아오게 된다. 만약 포함되어 있지 않다면 CORS에러가 발생한다.
토큰 등 사용자 식별 정보가 담긴 요청에 대해서는 보다 엄격하다. 보내는 측에서는 요청의 옵션에 credentials 항목을 true로 세팅하고, 받는 쪽에서도 보내는 쪽의 출처, 웹페이지 주소를 정확히 명시한 다음 Access-Control-Allow-Origin 항목을 true로 세팅해야 한다.
위 방식은 Simple request라고 해서 GET
이나 POST
등 일정 조건의 요청들에 사용되고 PUT
이나 DELETE
등의 요청들은 본 요청을 보내기 전에 Preflight request를 먼저 보내서 본 요청이 안전한지 확인하고 안전하다고 판단되야 본 요청을 보낼 수 있다.
참고
https://youtu.be/bW31xiNB8Nc
https://developer.mozilla.org/ko/docs/Web/HTTP/CORS