const-tommy.dev
기록을 불러오는 중입니다
웹 개발을 하며 가장 빈번하게 마주치는 에러가 바로 CORS다. 단순히 "서버에서 헤더 하나 열어주면 해결되는 귀찮은 문제"로 치부하기 쉽지만, 사실 이는 브라우저가 사용자의 데이터를 보호하기 위해 설계한 정교한 보안 메커니즘이다. 우리가 왜 이 에러를 마주해야만 하는지, 그 근원인 SOP부터 파헤쳐 보자.
SOP(Same-Origin Policy) 는 "어떤 출처에서 불러온 문서나 스크립트가 다른 출처의 리소스와 상호작용하는 것을 제한"하는 브라우저의 핵심 원칙이다.
브라우저는 다음 세 가지가 단 하나라도 다르면 서로 다른 출처로 간주하고 통제한다.

http와 https는 엄연히 다르다.80포트와 3000포트는 다른 집이다.책에서 언급한 '아파트 비유' 처럼, 102호 사람이 201호의 창문을 열어 내부 물건을 훔쳐보는 행위를 막는 것과 같다.
fetch, XMLHttpRequest)로 교차 출처의 응답 데이터를 읽어오는 것.이러한 제약이 없다면 어떤 끔찍한 일이 벌어질까? SOP가 사라진 세상을 상상해 보면 브라우저의 고집이 이해가 가기 시작한다.
SOP가 사라지면 우리가 신뢰하는 웹의 안전망은 순식간에 붕괴된다.
인터넷 뱅킹과 Gmail에 로그인된 상태에서 악성 사이트 https://cute-cat.com에 접속하면, 배경에서 조용히 내 계좌 잔고를 fetch로 읽어 해커의 서버로 전송할 수 있다. 브라우저에 저장된 내 인증 쿠키 가 자동으로 함께 실려 가기 때문이다.
SOP가 없다면, 악성 블로그 글을 읽는 것만으로도 내 SNS 계정이 도용되어 피싱 링크가 담긴 게시글이 자동으로 올라갈 수도 있다.
책에서는 우리가 안심하고 웹을 쓸 수 있는 이유가 바로 브라우저가 "당신은 당신의 집 안에서만 활동할 수 있다" 는 SOP를 강제하고 있기 때문이라고 강조한다.
하지만 현대의 웹은 서로 다른 도메인 간의 협력이 필수적이다. 프런트엔드와 API 서버의 출처가 다른 상황에서, 보안을 지키며 문을 열어주는 공식 통행증이 바로 CORS다.
CORS는 브라우저와 서버가 나누는 '신뢰의 대화' 다.
Origin)를 자동으로 실어 보낸다.Access-Control-Allow-Origin 헤더에 허용할 출처를 명시하여 응답한다.Origin과 일치하면 데이터를 전달한다.브라우저는 요청의 위험도에 따라 대화 방식을 바꾼다.
application/json은 사전 요청 대상이다.DELETE처럼 위험한 요청은 본 요청 전 OPTIONS 메서드로 서버의 허가를 먼저 구한다.하지만 대화가 통과되었다고 다 끝난 것은 아니다. 로그인 정보(Cookie)를 주고받거나 특별한 헤더를 읽어야 한다면 대화의 난이도는 한 층 더 높아진다.
쿠키나 인증 헤더가 포함된 요청은 훨씬 엄격한 규칙이 적용된다.
withCredentials: true 옵션이 필수다.Access-Control-Allow-Credentials: true 응답이 필수이며, 와일드카드(*) 설정은 절대 허용되지 않는다.서버가 보낸 커스텀 헤더나 토큰을 프런트엔드에서 읽어야 한다면, 서버는 반드시 Access-Control-Expose-Headers 에 해당 이름을 명시해야만 한다.
이 모든 이론이 합쳐진 '최종 대화'는 네트워크 탭에서 다음과 같이 관찰된다.
1. 브라우저의 질문 (OPTIONS)
안녕하세요 서버님,
https://my-service.com에서 왔습니다.Authorization헤더와DELETE요청을 보내도 될까요?
2. 서버의 답변 (200 OK)
확인되었습니다! 해당 출처에서 말씀하신 메서드와 헤더 사용을 허용합니다.
HTTP/1.1 200 OK
Access-Control-Allow-Origin: [https://my-service.com](https://my-service.com)
Access-Control-Allow-Methods: GET, POST, DELETE
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 86400이론을 넘어 실제 서버 환경에서 어떻게 안전하게 문을 열어줄 수 있을까? 책 11페이지에서는 실무에서 가장 권장되는 동적 화이트리스트 관리 방식을 소개한다. 단순히 하나의 도메인을 허용하는 것을 넘어, 로컬 개발 환경과 실제 운영 환경을 모두 고려한 유연한 설정이다.
// 서버(Node.js/Express)의 올바른 CORS 설정 예시
const whitelist = ['[https://my-service.com](https://my-service.com)', 'http://localhost:3000'];
const corsOptions = {
origin: function (origin, callback) {
// 요청 출처가 화이트리스트에 있거나, 서버 간 통신(origin 없음)일 경우 허용
if (whitelist.indexOf(origin) !== -1 ||
이 방식의 핵심은 요청이 들어올 때마다 origin을 화이트리스트와 대조하여 동적으로 허용 여부를 결정한다는 점이다. 앞서 살펴본 것처럼 인증 정보(Credentials)를 포함할 때는 와일드카드(*)를 쓸 수 없기에, 이런 동적 검증 방식은 실제 서비스 운영에서 필수적인 테크닉이다.
결국 CORS 에러는 우리를 괴롭히는 장벽이 아니라, 서버와 브라우저가 사용자의 안전을 위해 나누는 정중한 대화다. 에러 메시지가 떴을 때 당황하거나 무작정 서버 담당자에게 문을 열어달라고 하기보다, 개발자 도구의 네트워크 탭을 열어보자.
브라우저와 서버가 나누는 대화의 어떤 부분이 어긋났는지 정확히 짚어낼 수 있을 때, 우리는 단순한 코더를 넘어 웹의 근본적인 보안 메커니즘을 깊이 이해하는 진정한 엔지니어로 거듭날 수 있다. 책을 통해 배운 이 '대화의 기술'이 앞으로의 프로젝트에서 마주할 수많은 보안 이슈를 해결하는 든든한 밑거름이 되길 바란다.