CORS
Cross-Origin Resource Sharing is a mechanism that allows restricted resources on a web page to be requested from another domain.
— W3C CORS 공식 표준 명세서 개요
서버 개발자는 '다 열어놨는데 왜 안 되냐'고 외치고, 브라우저는 '출처가 달라서 거절한다'고 외치며, 프론트 개발자는 양쪽 사이에서 머리를 뜯는 불통의 아이콘. 로컬 개발 환경에서 이 시뻘건 에러를 보고 화가 나 결국 Access-Control-Allow-Origin: *을 날려버리고 보안을 포기하고 싶어진다.(...)
1. 개요
웹 브라우저가 제공하는 철저한 보안 장치인 SOP (Same-Origin Policy, 동일 출처 정책)을 우회하여, 허용된 외부 출처(Origin)의 리소스에 안전하게 접근할 수 있도록 도와주는 브라우저 보안 메커니즘. 기본적으로 웹 생태계는 보안상의 이유로 다른 도메인으로의 요청을 원천 차단하지만, API 서버가 분리된 현대적인 웹 애플리케이션 환경에서는 필수적으로 교차 출처 리소스 공유를 허용해 주어야 한다. 이를 위해 브라우저와 웹 서버 간에 특정 HTTP 헤더들을 주고받는 합의 체계가 바로 CORS다.
2. 동작 메커니즘과 프리플라이트 (Preflight)
CORS의 동작은 크게 세 가지로 분류된다. 가장 악명 높은 것이 바로 프리플라이트 요청(Preflight Request)으로, 브라우저가 실제 요청을 보내기 전에 안전성 확인을 위해 HTTP OPTIONS 메서드로 미리 간을 보는 예비 요청을 날리는 것이다. 이때 서버가 응답 헤더인 Access-Control-Allow-Origin 등을 통해 허용 목록을 보내지 않으면, 브라우저는 실제 요청을 보내지도 않고 사정없이 에러를 뿜는다.1 반면 단순 요청(Simple Request)은 예비 요청 없이 직접 가지만 조건이 매우 까다로워 현대 앱에서는 거의 발동하기 어렵다. 마지막으로 인증 정보를 포함하는 신용 요청(Credentialed Request)의 경우, 서버와 클라이언트 모두가 한 치의 오차도 없이 신용 설정을 맞춰야만 동작하는 극도의 정합성을 요구한다.
3. 프론트엔드와 백엔드의 환상의 핑퐁 게임
CORS 이슈가 터졌을 때 프론트엔드 개발자는 브라우저 콘솔의 붉은 에러 메시지만 보고 백엔드 개발자에게 해결을 요구하게 된다. 백엔드 개발자는 로컬이나 백엔드 코드상에서 포스트맨 등으로 API를 찌르면 너무나도 멀쩡히 동작하기 때문에 처음에는 프론트엔드의 빌드나 코드 오류인 줄 알고 반려하는 촌극이 흔히 벌어진다. 이 에러는 오직 브라우저 내부에서만 발동하는 보안 규칙이기 때문에 서버 단독 테스트로는 잡히지 않으며, 반드시 서버 설정에 CORS 허용 헤더를 세밀하게 주입하거나, Nginx 등의 리버스 프록시 서버 설정을 만져 해결해야 한다.
4. 관련 밈 및 드립
4.1. Access-Control-Allow-Origin: *
CORS 지옥에 갇힌 초보 개발자들이 절망에 빠진 끝에 선택하는 금단의 마법. 모든 도메인에서의 교차 출처 요청을 전면 무상 상태로 허가해 주는 설정이다. 로컬 개발 환경에서 빠르게 탈출하기 위해 이 치트키를 쓰는 경우가 많으나, 만약 이대로 실 운영 환경에 배포했다가는 악성 사이트가 우리 서버의 소중한 유저 리소스를 빨아들이는 해킹의 무대가 열린다.(...) 보안 담당자에게 적발되는 즉시 전사적인 시말서를 작성하게 될 것이다.
4.2. OPTIONS는 요청을 두 번 날리는 범인
프리플라이트 때문에 백엔드 API 서버의 부하가 눈 깜짝할 사이에 두 배로 치솟으며 발생하는 드립. 요청 하나를 보낼 때마다 OPTIONS 예비 요청이 기계처럼 무조건 선행되기 때문에, 서버 개발자들은 불필요한 트래픽 낭비라며 절규하곤 한다. 이를 막기 위해 브라우저 캐싱용 헤더(Access-Control-Max-Age)를 세밀하게 내려보내 예비 요청을 캐싱하는 센스가 시니어의 기본 덕목으로 꼽힌다.
5. 여담
- SOP와의 환상적인 관계: CORS는 SOP라는 더 거대한 원천 보안 법칙 아래에 존재하는 일종의 '합의된 예외 규정'이다. SOP가 집주인의 엄격한 무단침입 방지책이라면, CORS는 사전에 승인된 배달원에게 인터폰을 열어주는 예외 통로인 셈이다.
- 가장 억울한 주체, 백엔드: 브라우저는 자바스크립트가 다른 서버의 데이터를 가져가 보안 침해가 나는 것을 막기 위해 이 에러를 프론트엔드 개발자 눈앞에 뿌려준다. 그러나 해결을 위해 CORS 허용 정책을 직접 설정해 주어야 하는 것은 결국 백엔드 서버다.(...)
- Postman은 면역이다: 로컬 브라우저에서 'CORS 오류'라며 철저하게 접근이 차단되던 API도 Postman이나 curl 명령어로 터미널에서 호출하면 아무런 에러 없이 훌륭하게 JSON 데이터를 반환한다. CORS는 브라우저가 자바스크립트 실행 엔진 수준에서 보장하는 규칙이지, 서버의 통신 자체가 물리적으로 차단된 것이 아니기 때문이다.
6. 관련 문서
각주
-
실제로 로컬 호스트 포트 번호(
http://localhost:3000->http://localhost:8080)만 달라도 브라우저 입장에서는 완전히 남남인 교차 출처로 인식한다. ↩