본문 바로가기
CLOUD/AWS

CORS “보여주기 vs 읽기”, 프리플라이트, S3·CloudFront 정리

by Rainbound-IT 2025. 8. 28.
반응형

 

 

  • 다른 오리진 리소스를 “그려 넣기(표시/적용)”만 하면 보통 CORS 불필요: <img>, <script>, <link rel="stylesheet">, <video> 등.
  • JS가 응답 “내용을 읽는 요청”(예: fetch/XHR, <canvas> 픽셀 읽기, 웹폰트)은 브라우저가 SOP로 막음 → 서버가 CORS 헤더로 예외 허용해야 함. MDN Web Docs,2
  • 프리플라이트(OPTIONS) 는 “단순 요청(simple request)”이 아니면 뜸(예: Content-Type: application/json, 커스텀 헤더, PUT/DELETE 등). MDN Web Docs, 2
  • Credentials(쿠키/서명/Authorization) 사용 시 Access-Control-Allow-Origin: * 금지, 정확한 오리진을 돌려야 함 + 필요 시 Vary: Origin. MDN Web Docs
  • CloudFront에선 응답 헤더 정책(Managed CORS) + 캐시 정책으로 Origin/프리플라이트 헤더 전달을 설정해야 캐시에 섞이지 않음. AWS Documentation+1,2

1) Same-Origin Policy(SOP)와 “보여주기 vs. 읽기”

  • SOP: 문서/스크립트가 다른 오리진 응답의 “내용”을 읽는 것을 제한. CORS는 서버가 그 제한을 선택적으로 완화하는 표준. MDN Web Docs, 2
  • 실무 기억법:
    • 보여주기(단순 삽입) → 대체로 OK: <img>, <script>, <link>, <video>, <audio>, <iframe>(단, DOM 접근은 SOP로 차단)
    • 읽기(응답 바디/픽셀/헤더를 JS가 봄) → CORS 필요: fetch/XHR, <canvas> 픽셀 읽기, 웹폰트 등

캔버스 예외(“오염/tainting”)
다른 오리진 이미지/비디오를 CORS 승인 없이 <canvas>에 그리면 픽셀 읽기/내보내기 실패. 해결: <img crossorigin="anonymous"> + 서버가 Access-Control-Allow-Origin. MDN Web Docs, 2


2) 프리플라이트(OPTIONS)는 언제 뜨나?

브라우저는 “단순 요청”(safelisted)만 프리플라이트 없이 보냄:

  • 메서드: GET, HEAD, POST
  • 헤더: Accept, Accept-Language, Content-Language, Content-Type(단, application/x-www-form-urlencoded | multipart/form-data | text/plain 범위) 등 safelisted 헤더만 포함해야 함. 그 외(예: Content-Type: application/json, 커스텀 헤더, PUT/DELETE)면 사전 점검용 OPTIONS가 나감. fetch.spec.whatwg.orgMDN Web Docs

프리플라이트 결과는 Access-Control-Max-Age 동안 캐시 가능(브라우저 제한 존재). MDN Web Docs


3) Credentials와 와일드카드(*)의 금기

  • 쿠키/서명 URL/Authorization자격 증명을 포함하려면:
    • 요청: credentials: 'include'
    • 응답: Access-Control-Allow-Credentials: true 그리고 Access-Control-Allow-Origin은 정확한 오리진 값(와일드카드 * 사용 불가). 필요 시 **Vary: Origin**으로 동적 오리진 대응. MDN Web Docs+2MDN Web Docs, 2 ,3 

4) S3 + CloudFront에서 CORS, 이렇게 세팅

A. CloudFront “응답 헤더 정책” 우선

  • AWS Managed Policy: CORS-With-Preflight(또는 보안 헤더 포함 버전) 사용 → CORS 헤더 일괄 주입, 프리플라이트도 처리. AWS Documentation
  • origin_override(정책이 원본 헤더를 덮어씀) 동작을 이해하고 적용. AWS Documentation

B. CloudFront “캐시 정책”에서 헤더 포워딩

  • CORS가 오리진별로 달라지면 캐시 키에 영향:
    • 최소 Origin 헤더(그리고 프리플라이트 캐시 시 Access-Control-Request-* 헤더) 포워딩/키 포함을 구성해야 오리진 섞임(cache poisoning) 방지. AWS Documentation

C. S3 버킷 CORS(보조용)

  • 필요 시 S3에 CORS 규칙: 특정 오리진/메서드/헤더 허용. AllowedOrigins엔 http://*.example.com 같은 단 한 개의 * 와일드카드만 사용 가능. AWS Documentation

예시(JSON, aws s3api put-bucket-cors):

 
{
  "CORSRules": [
    {
      "AllowedOrigins": ["https://a.example.com", "https://b.example.com"],
      "AllowedMethods": ["GET", "HEAD", "OPTIONS"],
      "AllowedHeaders": ["*"],
      "ExposeHeaders": ["ETag"],
      "MaxAgeSeconds": 86400
    }
  ]
}
 

5) “a.example.com ↔ b.example.com”은 같은 사이트지만 같은 오리진이 아님

  • 오리진 판정은 scheme + host + port가 모두 같아야 same-origin. a.와 b.는 교차 오리진 → fetch/XHR·캔버스 픽셀 읽기·웹폰트 등은 CORS 대상.
  • 단, **Same-Site(쿠키 규칙)**는 eTLD+1 기준으로 다룸(둘 다 https면 same-site)이라 CORS와는 별개. 핵심: 쿠키가 가도 “읽기”는 CORS 허용 없으면 막힌다. MDN Web Docs

6) 폰트/미디어/iframe에서 자주 생기는 오해

  • 웹폰트(@font-face): 브라우저가 엄격 → CORS 필요한 경우가 흔함(Access-Control-Allow-Origin). Stack Overflow
  • 비디오/오디오: 재생만은 OK, 캔버스 픽셀 읽기면 이미지와 동일 규칙. MDN Web Docs
  • iframe이 안 뜨는 것은 보통 CORS가 아니라 X-Frame-Options/CSP frame-ancestors 때문. MDN Web Docs, 2

7) CloudFront/ALB에서 OPTIONS 403이 뜬다면

  • CloudFront 캐시 동작에서 OPTIONS 허용 + 오리진으로 전달 필요.
  • 백엔드가 OPTIONS에 200을 줄 수 있게 라우팅/미들웨어를 추가. (단순히 Allow 헤더만 보내도 됨) Server Fault

8) 디버깅 체크리스트

  1. DevTools Network에서 실제로 막힌 요청이 GET인지 OPTIONS인지, 그리고 응답 헤더(Allow-Origin/Methods/Headers/Max-Age)가 맞는지 확인. MDN Web Docs
  2. Credentials 사용 여부 확인 → Allow-Credentials: true + 정확한 Origin 반환, * 금지. MDN Web Docs
  3. 프리플라이트 트리거 요소가 있는지: Content-Type: application/json, 커스텀 헤더, PUT/DELETE 등. MDN Web Docs
  4. 캐시 섞임 방지: CloudFront 캐시 정책에 Origin(+ 필요 시 Access-Control-Request-*) 포함, 또는 Vary: Origin 사용. AWS DocumentationMDN Web Docs

9) 실전 스니펫 모음

(1) CloudFront — AWS Managed Policy 쓰기(콘솔 기준)

  • 응답 헤더 정책: “CORS-With-Preflight” 또는 “CORS-With-Preflight-and-SecurityHeaders” 적용
  • 캐시 정책: Origin 헤더(프리플라이트 캐시 시 Access-Control-Request-Method/Headers) 포워딩/키 포함. AWS Documentation, 2

(2) NGINX(백엔드) — 프리플라이트 응답

location /api/ {
  # 실제 요청에 대한 CORS 헤더
  add_header Access-Control-Allow-Origin $http_origin always;
  add_header Access-Control-Allow-Credentials true always;

  # 프리플라이트 처리
  if ($request_method = OPTIONS) {
    add_header Access-Control-Allow-Origin $http_origin;
    add_header Access-Control-Allow-Credentials true;
    add_header Access-Control-Allow-Methods "GET,POST,PUT,DELETE,OPTIONS";
    add_header Access-Control-Allow-Headers "*";
    add_header Access-Control-Max-Age 86400;
    return 204;
  }
  proxy_pass http://app;
}
 

 

실제로는 애플리케이션 레벨에서 처리해도 OK. CloudFront 앞단이라면 응답 헤더 정책으로 헤더를 붙이는 방향이 더 깔끔.

(3) S3 — CORS 구성(JSON)

{
  "CORSRules": [
    {
      "AllowedOrigins": ["https://a.example.com", "https://b.example.com"],
      "AllowedMethods": ["GET", "HEAD", "OPTIONS"],
      "AllowedHeaders": ["*"],
      "MaxAgeSeconds": 86400
    }
  ]
}
 

AllowedOrigins에 *는 Credentials 사용 시 불가. 여러 오리진을 허용하면 Vary: Origin 고려. MDN Web Docs+1


10) 추가로 헷갈리기 쉬운 것들

  • mode: 'no-cors': 요청은 가지만 **응답이 “opaque”**라 JS가 바디/헤더를 못 읽음. 대부분의 앱 로직엔 부적합(서비스 워커 특정 케이스용). MDN Web Docs
  • CORS ≠ 보안 헤더 전부: CORP(Cross-Origin-Resource-Policy), COEP/COOP, CSP 등은 별도 정책. 에러 메시지로 구분하자. MDN Web Docs

마무리

  • 표시/적용은 자유, 읽기는 허가제 — 이 한 줄이면 CORS 반은 끝났어요.
  • CloudFront를 쓴다면 응답 헤더 정책 + 캐시 정책이 세트, S3는 부가적으로 CORS를 맞추면 됩니다.
  • Credentials가 얽히면 정확한 오리진을 반환하고, Vary: Origin과 캐시 구성을 신경 쓰면 대부분의 꼬임이 풀립니다. AWS Documentation, 2, MDN Web Docs

 

반응형

댓글