CORS란 무엇인가요?

교차 출처 리소스 공유(CORS)는 웹 페이지가 페이지를 제공한 도메인과 다른 도메인에 요청하는 것을 제한하는 브라우저 보안 메커니즘입니다. 프론트엔드(예: https://myapp.com)가 다른 도메인(예: https://api.example.com)에 API 호출을 할 때, 브라우저는 먼저 서버가 이를 명시적으로 허용하는지 확인합니다.

서버가 응답에 적절한 Access-Control-Allow-Origin 헤더를 포함하지 않으면 브라우저가 요청을 차단하고 CORS 에러를 발생시킵니다. 중요한 점은 CORS는 브라우저에서 적용됩니다 — 요청 자체는 서버에 도달하지만, 브라우저가 JavaScript에서 응답을 읽는 것을 차단합니다.

일반적인 CORS 에러

  • 'Access-Control-Allow-Origin' 헤더 없음: 서버 응답에 CORS 헤더가 완전히 누락되었습니다. 해결: 서버 응답에 헤더를 추가하세요.
  • Preflight 요청 실패: 커스텀 헤더나 단순하지 않은 메서드(PUT, DELETE)를 사용하는 요청의 경우 브라우저가 OPTIONS preflight 요청을 보냅니다. 서버에서 이에 올바르게 응답해야 합니다.
  • Credentials 플래그: withCredentials: true로 쿠키나 Authorization 헤더를 사용할 때, 서버는 Access-Control-Allow-Credentials: true를 반환해야 하며 출처가 특정되어야 합니다(* 사용 불가).
  • 허용된 헤더 불일치: 요청에 Access-Control-Allow-Headers에 나열되지 않은 헤더가 포함되면 preflight가 실패합니다.

프레임워크별 CORS 해결 방법

위의 도구를 사용하여 백엔드에 맞는 정확한 수정 코드를 생성하세요. 모든 프레임워크에서 기본 원칙은 동일합니다: 서버가 적절한 Access-Control-Allow-* 헤더를 반환하도록 설정하세요. 프로덕션 환경에서는 와일드카드(*) 대신 항상 정확한 허용 출처를 지정하세요. 특히 인증된 요청을 처리할 때 중요합니다.

CORS 프리플라이트 메커니즘 이해하기

브라우저가 "단순"하지 않은(기본 헤더가 있는 GET/POST 이외의) 크로스 오리진 요청을 할 때, 먼저 OPTIONS 프리플라이트 요청을 보내 서버에 권한을 요청합니다. 이 2단계 프로세스가 대부분의 CORS 혼란의 원인입니다.

프리플라이트 요청을 트리거하는 것

  • 커스텀 헤더: Accept, Content-Type(단순 값인 경우), Content-Language 이외의 헤더는 프리플라이트를 트리거합니다. 흔한 트리거: Authorization, X-Requested-With, X-API-Key
  • 비단순 메서드: PUT, DELETE, PATCH는 모두 프리플라이트를 트리거합니다. GET과 폼 데이터를 사용한 POST는 트리거하지 않습니다.
  • 단순 값을 넘는 Content-Type: application/json은 프리플라이트를 트리거하지만, application/x-www-form-urlencoded은 트리거하지 않습니다. JSON 바디를 사용한 fetch가 같은 엔드포인트에서 폼 데이터로는 작동하는 이유입니다.

CORS 디버깅 체크리스트

CORS 오류를 만났을 때, 다음 체크리스트를 순서대로 확인하세요:

  • 1. 브라우저 콘솔 확인: 오류 메시지가 어떤 헤더가 누락되었거나 잘못 구성되었는지 정확히 알려줍니다
  • 2. OPTIONS 응답 검사: DevTools → 네트워크 탭 → 실패한 요청 필터 → OPTIONS 응답에 올바른 Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers가 포함되어 있는지 확인
  • 3. 오리진이 정확히 일치하는지 확인: http://localhost:3000http://localhost:3001은 다른 오리진입니다. 후행 슬래시도 중요합니다.
  • 4. 자격 증명 모드 확인: 쿠키를 보내는 경우, 서버는 Access-Control-Allow-Credentials: true로 응답해야 하며, Allow-Origin에 와일드카드(*)를 사용할 수 없습니다
  • 5. 프록시가 헤더를 제거하지 않는지 확인: 리버스 프록시(Nginx, Cloudflare, AWS ALB)가 CORS 헤더를 제거하거나 덮어쓰는 경우가 있습니다

프로덕션에서 흔한 CORS 실수

가장 빈번한 프로덕션 CORS 문제에는: 자격 증명과 함께 Access-Control-Allow-Origin: * 사용(자동으로 실패), 서버리스 함수(Lambda, Vercel)에서 OPTIONS 요청 처리를 잊는 것, Allow-Origin 헤더를 동적으로 설정할 때 Vary: Origin을 포함하지 않는 것(CDN 캐싱 문제 유발)이 포함됩니다. 또 다른 흔한 실수는 개발 환경에서 CORS를 수정하지만 헤더를 제거하는 다른 프록시 구성으로 배포하는 것입니다.

CORS에 대한 자주 묻는 질문

CORS 에러의 원인은 무엇인가요?

CORS 에러는 서버 응답에 Access-Control-Allow-Origin 헤더가 없어 브라우저가 교차 출처 요청을 차단할 때 발생합니다. 브라우저는 동일 출처 정책을 적용합니다 — 다른 도메인, 포트 또는 프로토콜에서의 요청은 CORS preflight 검사를 트리거합니다. 중요한 점은 CORS는 브라우저에서만 적용되며, 요청 자체는 서버에 도달하지만 브라우저가 JavaScript에서 응답을 읽는 것을 차단합니다.

Express.js에서 CORS를 어떻게 해결하나요?

cors 패키지를 설치하고(npm install cors) app.use(cors())로 모든 출처를 허용하거나 정확한 출처를 지정하세요: app.use(cors({ origin: 'https://yourdomain.com' })). 쿠키나 Authorization 헤더가 포함된 요청의 경우 credentials: true를 설정하고 허용 출처에 와일드카드(*)를 사용하면 안 됩니다 — 정확한 도메인을 지정해야 합니다.

CORS preflight 요청이란 무엇인가요?

Preflight는 커스텀 헤더나 단순하지 않은 HTTP 메서드(PUT, DELETE, PATCH)를 사용하는 교차 출처 요청 전에 브라우저가 자동으로 보내는 HTTP OPTIONS 요청입니다. 서버는 Access-Control-Allow-MethodsAccess-Control-Allow-Headers 헤더, 그리고 200 또는 204 상태 코드로 OPTIONS에 응답해야 합니다. Preflight가 실패하면 실제 요청은 전송되지 않습니다.

Postman에서는 CORS가 작동하는데 브라우저에서는 왜 안 되나요?

CORS는 브라우저 보안 메커니즘으로, 서버 간 통신이나 Postman, curl, REST 클라이언트 같은 도구에는 적용되지 않습니다. Postman은 동일 출처 정책을 적용하지 않고 직접 요청을 보내므로 요청이 성공합니다. 브라우저에서는 서버가 CORS 헤더를 통해 명시적으로 허용하지 않는 한 JavaScript가 교차 출처 응답을 읽는 것이 제한됩니다.

관련 개발자 도구