반응형
- 다른 오리진 리소스를 “그려 넣기(표시/적용)”만 하면 보통 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) 디버깅 체크리스트
- DevTools Network에서 실제로 막힌 요청이 GET인지 OPTIONS인지, 그리고 응답 헤더(Allow-Origin/Methods/Headers/Max-Age)가 맞는지 확인. MDN Web Docs
- Credentials 사용 여부 확인 → Allow-Credentials: true + 정확한 Origin 반환, * 금지. MDN Web Docs
- 프리플라이트 트리거 요소가 있는지: Content-Type: application/json, 커스텀 헤더, PUT/DELETE 등. MDN Web Docs
- 캐시 섞임 방지: 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
반응형
'CLOUD > AWS' 카테고리의 다른 글
| EKS kubectl 연결 오류 해결기: NAT 교체 이후 발생한 함정 (0) | 2025.09.22 |
|---|---|
| AWS Network Loadbalancer의 ALPN이란? (0) | 2025.09.09 |
| [AWS IAM] 사용자에게 S3 전체 버킷 조회 + 객체 Get/Put/Delete 권한 부여하기 (1) | 2025.08.26 |
| CloudFront + S3로 이미지 서빙하기: 한 배포, 여러 도메인, 경로 기반 라우팅 (3) | 2025.08.25 |
| AWS CloudFront 대체 도메인(Alternate Domain Names)와 Route 53 A 레코드 정리 (0) | 2025.08.20 |
댓글