liminfo

CORS Guide & Debugger

CORS 정책 가이드 - preflight 흐름, 프레임워크별 설정

15개 결과

CORS Guide & Debugger 소개

CORS 레퍼런스는 프론트엔드-백엔드 애플리케이션을 개발하는 모든 웹 개발자가 만나게 되는 교차 출처 리소스 공유(CORS) 명세 전체를 검색 가능한 형태로 정리한 자료입니다. 헤더 섹션에서는 6가지 CORS 응답 헤더를 모두 다룹니다: 허용 출처를 지정하는 Access-Control-Allow-Origin(정확한 도메인 또는 와일드카드 *), 허용 HTTP 메서드를 지정하는 Access-Control-Allow-Methods, Authorization 및 Content-Type 같은 커스텀 요청 헤더를 허용하는 Access-Control-Allow-Headers, 쿠키 및 인증 헤더 전송을 활성화하는 Access-Control-Allow-Credentials, JavaScript에 커스텀 응답 헤더를 노출하는 Access-Control-Expose-Headers, Preflight 결과를 캐시하는 Access-Control-Max-Age.

요청 유형 섹션에서는 단순 요청과 Preflight 요청의 핵심 차이를 설명합니다. 단순 요청(GET, HEAD, application/x-www-form-urlencoded 같은 안전한 Content-Type을 사용하는 POST)은 Preflight 없이 직접 전송됩니다. PUT/DELETE/PATCH를 사용하거나, Authorization 같은 커스텀 헤더를 포함하거나, Content-Type: application/json을 사용하는 요청은 Preflight를 유발합니다. Preflight는 브라우저가 실제 요청 허용 여부를 확인하기 위해 먼저 보내는 OPTIONS 요청입니다. 서버 설정 섹션에서는 4가지 주요 서버 환경에 대한 즉시 사용 가능한 CORS 설정 코드를 제공합니다: Express(cors npm 패키지), Django(django-cors-headers), Spring Boot(@CrossOrigin 어노테이션 또는 WebMvcConfigurer), Nginx(add_header 지시자와 OPTIONS 처리).

트러블슈팅 섹션에서는 개발자가 가장 자주 마주치는 3가지 CORS 오류를 다룹니다. Origin 불일치는 서버가 Access-Control-Allow-Origin 헤더를 반환하지 않거나 값이 요청의 Origin 헤더와 일치하지 않을 때 발생합니다. Credentials 오류는 Access-Control-Allow-Credentials: true와 Access-Control-Allow-Origin: *을 함께 설정할 때 브라우저가 항상 차단하는 문제로, 정확한 출처를 명시해 해결합니다. Preflight 실패는 서버가 OPTIONS 요청을 처리하지 않을 때 발생합니다. 이 레퍼런스는 프론트엔드 개발자, API 설계자, WAF나 리버스 프록시를 설정하는 DevOps 엔지니어에게 필수적인 자료입니다.

주요 기능

  • 올바른 구문과 예제를 포함한 6가지 CORS 응답 헤더 전체
  • Access-Control-Allow-Credentials 동작과 와일드카드 출처 제한
  • 단순 요청 조건(안전한 메서드, 안전한 Content-Type, 커스텀 헤더 없음)
  • Access-Control-Request-Method와 -Headers를 포함한 Preflight OPTIONS 요청 구조
  • origin 콜백과 credentials를 포함한 Express cors() 미들웨어 설정
  • Django django-cors-headers CORS_ALLOWED_ORIGINS와 CORS_ALLOW_CREDENTIALS 설정
  • Spring Boot @CrossOrigin 어노테이션과 WebMvcConfigurer.addCorsMappings()
  • OPTIONS 메서드 204 처리를 포함한 Nginx add_header CORS 설정

자주 묻는 질문

브라우저에서는 CORS 오류가 나지만 Postman에서는 왜 문제없이 작동하나요?

CORS는 서버나 네트워크 제한이 아니라 브라우저 보안 정책입니다. Postman은 브라우저가 아니므로 동일 출처 정책을 적용하지 않아 교차 출처 요청을 자유롭게 보낼 수 있습니다. 브라우저는 JavaScript에서 발생하는 교차 출처 요청에 자동으로 Origin 헤더를 추가하고 CORS 핸드셰이크를 강제합니다. 그래서 CORS 오류는 브라우저 DevTools에서만 나타납니다.

Access-Control-Allow-Origin: *와 credentials를 함께 사용할 수 없는 이유는?

CORS 명세는 Access-Control-Allow-Credentials: true 설정 시 와일드카드 출처(*)를 명시적으로 금지합니다. 와일드카드를 허용하면 어떤 사이트든 로그인한 사용자의 쿠키나 인증 헤더를 포함한 요청을 API에 보낼 수 있어 보안 위협이 됩니다. 대신 정확한 출처를 명시해야 하며, 서버는 요청의 Origin 헤더를 허용 목록과 대조해 일치하면 그 출처를 동적으로 반영해야 합니다.

CORS Preflight 요청은 언제 발생하나요?

다음 조건 중 하나라도 충족하면 OPTIONS Preflight 요청이 발생합니다: (1) 메서드가 PUT, DELETE, PATCH, CONNECT인 경우, (2) Accept, Accept-Language, Content-Language, Content-Type 이외의 커스텀 헤더가 포함된 경우, (3) Content-Type이 application/x-www-form-urlencoded, multipart/form-data, text/plain 이외인 경우. JSON을 전송하거나(Content-Type: application/json) Authorization 헤더를 포함하는 대부분의 API 호출이 Preflight를 유발합니다.

브라우저에서 CORS 오류를 디버깅하는 방법은?

브라우저 DevTools의 Network 탭을 열고 실패한 요청을 확인합니다. 빨간색으로 표시된 "CORS error" 또는 "(blocked:other)" 상태의 요청을 클릭해 Response Headers 탭에서 Access-Control-Allow-Origin 헤더의 부재 여부를 확인합니다. Preflight 실패의 경우 실제 요청 직전의 OPTIONS 요청과 그 응답을 확인합니다. 브라우저 Console에는 어떤 헤더가 누락되었거나 잘못된 값인지 명시하는 오류 메시지가 표시됩니다.

Access-Control-Max-Age는 어떻게 Preflight 요청을 줄이나요?

Access-Control-Max-Age는 브라우저가 Preflight 요청 결과를 캐시할 수 있는 시간(초)을 지정합니다. 이 시간 동안 동일한 엔드포인트, 동일한 메서드와 헤더를 사용하는 반복 요청은 새 Preflight를 유발하지 않습니다. 86400(24시간)으로 설정하면 자주 API를 호출하는 SPA의 네트워크 오버헤드를 크게 줄일 수 있습니다. 최대값은 브라우저마다 다르며 Chrome은 7200초, Firefox는 86400초로 제한합니다.

Nginx에서 백엔드 프레임워크 없이 CORS를 설정하는 방법은?

Nginx의 server 또는 location 블록 내에 CORS 헤더를 추가합니다. add_header Access-Control-Allow-Origin $http_origin으로 요청 출처를 동적으로 반영합니다. if ($request_method = OPTIONS) 블록을 추가해 Preflight 응답 헤더 설정 후 즉시 204를 반환합니다. $http_origin 변수를 Nginx map 블록과 함께 사용하면 출처를 허용 목록과 대조해 개방형 CORS 정책을 방지할 수 있습니다.

Access-Control-Allow-Headers와 Access-Control-Expose-Headers의 차이는?

Access-Control-Allow-Headers는 Preflight 응답에서 전송되며 브라우저에 클라이언트가 실제 요청에 포함할 수 있는 요청 헤더(예: Authorization, Content-Type)를 알려줍니다. Access-Control-Expose-Headers는 실제 응답에서 전송되며 JavaScript가 XMLHttpRequest 또는 Fetch API를 통해 읽을 수 있는 응답 헤더를 알려줍니다. 기본적으로 Content-Type 같은 소수의 "허용 목록" 응답 헤더만 JavaScript에서 읽을 수 있습니다.

와일드카드 없이 여러 특정 출처를 CORS에서 허용하는 방법은?

CORS는 응답당 Access-Control-Allow-Origin에 하나의 출처 값만 허용합니다. 여러 출처를 지원하려면 서버가 들어오는 요청의 Origin 헤더를 허용 목록과 비교해 일치하면 정확한 출처를 반영해야 합니다. Express에서는 cors의 origin 옵션을 함수로 설정해 callback(null, allowlist.includes(origin))을 호출합니다. Django에서는 CORS_ALLOWED_ORIGINS를 목록으로 설정하면 django-cors-headers가 자동으로 반영합니다.