응애 시절 개발자일 때는 코드 한 줄 한 줄 쳐 내려갈 때마다,
그에 따라 내가 작성한 코드가 화면에 잘 나오는 게 너무 좋았는데,
그러다 시뻘건 글자로 갑자기 공포감을 조성하는 에러 메시지를 볼 때마다 상당히 두려운 마음이 앞섰다.
혹자는 개발을 하다가 에러를 마주하면 오히려 즐겁다라고 하는 변태적인 성향을 드러냈었는데
최근에 나 역시 조금씩 에러가 좋아지고 있는걸 보면, 개발자는 다 그런가 보다.
아무튼 이번에 얘기해 볼 에러는 주니어 개발자 시절이라면 한 번쯤 겪어봤을 그 에러
CORS에러에 대해서 알아보려고 한다.
CORS란?
CORS는 Cross-Origin Resource Sharing 이라는 단어로 이루어진 축약어다.
단어를 풀이해 보자면 '교차 출처 리소스 공유 정책'이라고 해석할 수 있다.
여기서 출처라는 단어의 의미를 파악하면 CORS에 대해서 조금 더 쉽게 이해할 수 있다.
우리가 예를 들어 네이버에 접속했다고 가정해 보자.
그 후 주소창을 보면 https://www.naver.com이라는 주소를 볼 수 있다.
여기서 이 주소(URL)를 출처라고 하는데 이 URL은 여러 개의 요소로 구성되어있다.
그중 프로토콜, 도메인, 포트번호가 주된 구성요소인데,
어떤 URL끼리 출처를 비교해서 이 3가지가 동일하면 동일한 출처라고 인정한다.
그럼 다시 용어에 대한 정리를 해보면 CORS는 다른 URL의 리소스를 가져올 때의 정책을 의미하고,
CORS 에러는 이 정책을 위반했을 경우 에러 메시지를 내뱉게 된다.
다른 출처를 안 쓰면 되잖아?
사실 SOP (Same Origin Policy)라고 해서 동일한 출처에서만 리소스를 공유할 수 있게 하는 정책이 있다.
이 정책이 필요한 이유는 보안적인 이슈 때문인데, 만약 모든 출처의 상관없이 리소스를 공유하게 된다면,
악의적인 리소스를 받는데 큰 저항이 없게 될 거고, 이를 통해 발생하는 여러 문제점이 생길 수 있다.
그래서 SOP라고 하는 정책을 사용하게 됐는데, 사실 우리가 웹 사이트를 구성하는데 다른 출처의 리소스를
사용하지 않고 구현하는 것은 굉장히 어려운 일이다.
구글 폰트만 하더라도 다른 출처의 리소스를 가져다가 사용하는 것인데,
물론 자체적으로 폰트를 다운로드하여 입힐 수도 있겠지만,
번거로운 작업이고 상황에 따라 폰트가 적용되지 않는 경우도 있을 수 있으니
구글 폰트를 사용하는 것이 훨씬 간편한 상황이라고 볼 수 있다.
그래서 다른 출처의 리소스를 허용하는 정책이 등장했고 그것이 CORS 정책이라고 할 수 있다.
CORS 에러 판단은 누가..?
이전에는 CORS에러가 발생하면 쪼르르 백엔드 개발자에게 달려가 CORS 에러 났는데요..😢 하고
얘기하곤 해서 '이 에러는 서버가 요청을 처리하는 과정에서 발생하는 문제구나!' 하고 생각했는데
사실 이 출처를 비교하고 판단하는 것은 브라우저에서 처리한다.
이 동작 과정을 간략하게 살펴보면
우선 클라이언트에서 HTTP 요청의 헤더에 Origin을 담아서 전달한다.
서버는 응답 헤더에 Access-Control-Allow-Origin을 담아서 클라이언트로 전달한다.
응답을 받은 브라우저는 자신이 보낸 Origin과 서버에서 받은 Access-Control-Allow-Origin을 비교하는데
이 두 Origin이 서로 같지 않다면 CORS 에러를 뱉어내는 것이다.
이 과정을 살펴보니 역시 백엔드 개발자에게 달려가는 게 맞는 것 같다.
결국 응답 헤더에 출처를 클라이언트 요청 헤더에 담긴 URL과 맞추면 되는 것이기 때문이다.
근데 이 과정은 함께 개발을 하고 있는 상황이고, 웹 개발을 하다 보면 함께 만들고 있는 서버 외에도
외부 서버의 API를 사용하거나 다른 출처 간의 상호작용을 해야 하는 케이스도 종종 발생하곤 한다.
결국 Cross Origin에 대한 부분도 고려를 해야만 한다는 것이다.
CORS 작동 방식
위에서 살펴본 CORS 에러가 발생하는 방식은 기본적인 작동 흐름인데,
사실 CORS가 동작하는 방식은 3가지가 있다고 한다.
1. 예비 요청 (Preflight Request)
해당 방식은 CORS에러를 가장 많이 마주치는 방식이다.
클라이언트와 서버가 통신을 할 때, 그냥 바로 요청하고 바로 응답받는 방식이 아닌
해당 연결이 유효한지를 먼저 체크하는 방식으로 대부분 통신을 진행하는데,
이때, 브라우저는 요청을 예비 요청과 본 요청으로 나눠서 서버로 전송한다.
이 예비 요청 (Preflight)에서는 HTTP 메서드가 GET이나 POST가 아닌 OPTIONS이라는 요청이 사용이 된다.
이 과정에서 브라우저는 Origin을 담아서 보내고 응답받은 리소스에서 Access-Control-Allow-Origin을 비교해
일치하지 않을 경우 CORS 에러를 내뱉게 된다.
2. 단순 요청 (Simple Request)
단순 요청은 예비 요청이 없이 바로 본 요청을 보내는 방식이다.
이 요청은 대표적으로 아래 3가지 경우를 만족할 때 할 수 있는 방식이다.
1. 요청의 메서드는 GET, HEAD, POST 중 하나여야 한다.
2. Accept, Accept-Language, Content-Language, Content-Type, DPR, Downlink, Save-Data,
Viewport-Width, Width 헤더일 경우 에만 적용된다.
3. Content-Type 헤더가 application/x-www-form-urlencoded, multipart/form-data,
text/plain 중 하나여야 한다. 아닐 경우 예비 요청으로 동작된다.
이런 조건들이 있어야 하기 때문에 단순 요청이 일어나는 상황은 드물다고 보면 된다고 한다.
3. 인증된 요청 (Credentialed Request)
클라이언트에서 요청을 할 때 쿠키나 토큰 등의 자격 인증 정보를 함께 보낼 때 사용되는 방식이다.
이 방식에는 여러 옵션을 통해 관련된 정보를 담을 수 있는데, 이러한 정보를 통해 단순히 출처만 확인하는 것이 아니라
더 강화된 조건을 추가할 수 있게 된다.
CORS 에러 해결하기
결국 CORS 에러를 해결하는 게 가장 큰 문제가 아닐까 싶은데, 해당 문제를 해결하기 위한 몇 가지 방법이 있다.
우선 위에서 언급한 것처럼 클라이언트의 Origin과
서버에서 전달하는 응답의 Access-Control-Allow-Origin을 맞춰서 응답을 보내주면 해결이 가능하다.
물론 Access-Control-Allow-Origin에 *를 사용하면 만능이지만 보안적인 이슈가 발생할 수 있다.
그래서 Node.js의 Express는 CORS를 해결하는 라이브러리도 존재한다고 한다.
또 다른 방법으로는 Proxy 서버를 이용하는 방법이다.
Proxy 서버는 클라이언트와 서버 사이의 중간 서버라고 보면 되는데 이를 이용해 처리를 해줄 수 있다.
무료 프록시 서버도 있지만 악용 사례 때문에 (암튼 인간이 문제다) api 요청 횟수 제한을 두고 있다.
그래서 사용하려면 직접 구축하는 방법으로 사용해야 한다.
이렇게 해결했다고 해도 추가적으로 고민해야 할 문제가 있다고 하니
CORS 에러에 대한 밈이 많은 것도 이해가 된다..
포스팅 작성에 참고한 감사한 글들
- Inpa dev님의 블로그 : 악명 높은 CORS 개념 & 해결법
- 박수현님의 블로그 : 웹 개발자의 신고식, CORS로부터 해방되기
'CS > Web' 카테고리의 다른 글
DNS : ip 주소를 외우지 않아도 되는 이유 (0) | 2024.12.04 |
---|---|
브라우저의 동작 방법 : 주소창에 구글 주소를 입력 했을 때 (0) | 2024.11.16 |
CSR vs SSR (feat. NEXT.js의 등장) (0) | 2023.07.07 |