보안 헤더와 CORS
보안 헤더와 CORS
브라우저가 페이지를 그릴 때마다 여러 보호 메커니즘이 동시에 돕니다. 동일 출처 정책 · CORS preflight · CSP 의 스크립트 화이트리스트 · HSTS · Spectre 이후 도입된 격리 헤더. 이 글은 각 헤더의 역할과 자주 쓰이는 모양 · 점검 도구.
1. 동일 출처 정책
브라우저의 기본 보안 모델. 한 출처 (scheme://host:port) 의 스크립트가 다른 출처의 자원을 임의로 읽지 못하게. 이 위에 CORS · CSP · 격리 헤더가 쌓입니다.
2. CORS
다른 출처의 자원에 접근을 허용하는 메커니즘. RFC 6454 (Origin) · WHATWG Fetch Standard.
서버가 응답에 Access-Control-Allow-Origin 같은 헤더를 보내야 브라우저가 응답을 자바스크립트에 노출:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 86400
Preflight (OPTIONS) — 다음 조건 중 하나라도 해당하면 브라우저는 본 요청 전에 OPTIONS 메서드로 사전 요청:
- 단순 메서드 (GET · HEAD · POST) 가 아닌 경우.
Content-Type이 단순 값 (application/x-www-form-urlencoded·multipart/form-data·text/plain) 이 아닌 경우 (application/json도 preflight 대상).- 사용자 정의 헤더 (
Authorization외) 가 있는 경우.
서버는 OPTIONS 에 적절한 Access-Control-* 응답을 보내야 본 요청 진행.
Credentials — 쿠키 · HTTP 인증 · 클라이언트 인증서를 포함한 요청은 credentials: 'include' 필요. 이 경우 서버 응답의 Access-Control-Allow-Origin 은 * 일 수 없음 — 명시적 출처 필요. Access-Control-Allow-Credentials: true 도 함께.
3. CSP
W3C CSP Level 2 (2016) · Level 3 (진행). 브라우저에 "이 페이지에서 어떤 출처의 스크립트 · 스타일 · 이미지 · 연결을 허용하는가" 를 선언. XSS 의 영향 범위를 좁히는 가장 효과적인 도구.
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-rAnd0m';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
nonce · hash · strict-dynamic:
- nonce — 인라인 스크립트에 임의의 base64 nonce 를 넣고 CSP 에 같은 값 선언. 매 응답마다 새 값.
- hash — 인라인 스크립트의 SHA-256 해시를 CSP 에 등록.
- strict-dynamic — nonce 로 신뢰된 스크립트가 동적으로 로드한 스크립트도 신뢰. 화이트리스트 방식의 한계를 줄이는 모던 패턴.
script-src 'nonce-abc123' 'strict-dynamic';
Google 의 CSP Evaluator · CSP guide 가 strict-dynamic 모델 권장.
report-only — Content-Security-Policy-Report-Only 헤더로 위반을 차단하지 않고 보고만 받음. 신규 적용 시 회귀 점검 자리.
4. 다른 보안 헤더
HSTS (HTTP Strict Transport Security) — RFC 6797 (2012). HTTPS 강제:
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
max-age— 강제 기간 (초). 권장 1 년 이상.includeSubDomains— 서브도메인까지.preload— HSTS preload list 등재 후 브라우저가 첫 방문 전부터 강제.
preload 등록은 되돌리기 어렵다는 점이 자주 지적. 신중히.
X-Frame-Options 또는 frame-ancestors — iframe 임베딩 차단. clickjacking 방지:
X-Frame-Options: DENY
# 또는 CSP 의 frame-ancestors
Content-Security-Policy: frame-ancestors 'none'
CSP 의 frame-ancestors 가 더 표현력이 강해 X-Frame-Options 를 대체하는 흐름.
X-Content-Type-Options — 브라우저의 MIME sniffing 차단. application/json 응답이 HTML 로 해석돼 XSS 가 트리거되는 사고를 막음:
X-Content-Type-Options: nosniff
Referrer-Policy — 요청 시 Referer 헤더에 어디까지 정보를 담을지:
Referrer-Policy: strict-origin-when-cross-origin
no-referrer · same-origin · strict-origin-when-cross-origin (모던 기본값) 등.
Permissions-Policy — 이전 이름 Feature-Policy. 카메라 · 마이크 · 지오로케이션 등 브라우저 기능 사용을 제어:
Permissions-Policy: camera=(), microphone=(), geolocation=(self)
X-XSS-Protection — 레거시 헤더. 모던 브라우저는 무시 (Chrome 78에서 제거). CSP 가 대체.
5. 격리 헤더 (Spectre 이후)
2018 년 Spectre · Meltdown 이 하드웨어 사이드 채널 공격을 드러냄. 브라우저는 Cross-Origin Isolation 을 위해 새 헤더 도입.
COOP (Cross-Origin-Opener-Policy):
Cross-Origin-Opener-Policy: same-origin
다른 출처 윈도우와의 브라우저 컨텍스트 공유 차단. window.opener 같은 표면을 막음.
COEP (Cross-Origin-Embedder-Policy):
Cross-Origin-Embedder-Policy: require-corp
페이지가 로드하는 모든 자원이 명시적 권한 (CORP 헤더 또는 CORS) 을 가져야 함.
CORP (Cross-Origin-Resource-Policy):
Cross-Origin-Resource-Policy: same-site
자원 자신이 어떤 출처에서 임베드 가능한지 선언. COEP 환경에서 자원 측의 옵트인.
COOP + COEP 가 모두 충족되면 crossOriginIsolated 환경이 활성화되어 SharedArrayBuffer · 고해상도 타이머 같은 강력한 API 사용 가능. 일반 사이트에는 과한 수준일 수 있어 기능이 필요한 자리에 한정해 적용.
6. 미들웨어로 일괄
대부분의 프레임워크가 보안 헤더 미들웨어 제공:
- Express —
helmet. - Fastify —
@fastify/helmet. - Next.js —
next.config.js의headers()또는middleware.ts. - Spring Boot —
Spring Security의headers()DSL. - nginx · Caddy — 서버 설정에서 일괄.
helmet 의 기본값이 합리적인 출발점.
7. CORS 좁은 적용
- 출처 화이트리스트는 정확한 도메인.
- 와일드카드는 익명 API 가 아니면 피함.
- credentials + 와일드카드는 표준이 금지.
- preflight 캐시 (
Access-Control-Max-Age) 로 OPTIONS 비용 절감.
8. 점검 도구
- securityheaders.com — 브라우저 응답 헤더 점수.
- csp-evaluator.withgoogle.com — CSP 정책 분석.
- Mozilla Observatory — 종합 점검.
- 브라우저 개발자 도구의
Security·Network탭.
도입 후 정기 점검 — 새 기능 추가가 정책을 무력화할 수 있음.
9. 자주 걸리는 자리
CORS 의 OPTIONS 누락 — 백엔드가 OPTIONS 를 처리하지 않으면 모든 preflight 실패. 라우터 미들웨어로 일괄.
Access-Control-Allow-Origin: * + credentials — 표준 위반, 브라우저가 거부.
CSP 의 unsafe-inline — 사실상 XSS 보호가 사라짐. nonce / hash / strict-dynamic 으로.
HSTS preload 의 비가역성 — 잘못 설정하면 사용자 브라우저가 오랫동안 HTTP 접근을 거부.
X-Frame-Options 와 CSP frame-ancestors 의 충돌 — 두 헤더가 다른 정책을 보내면 더 엄격한 쪽이 적용되거나 브라우저별 동작이 다를 수 있음. 일관된 정책.
개발 환경의 느슨한 헤더가 운영에 새기 — 테스트용 와일드카드가 운영에 그대로 흘러가는 사고. 환경별 설정 분리.
nonce 재사용 — 매 응답 새 값이어야 의미. 정적 nonce 는 보호 효과 사라짐.
CDN 캐시와의 충돌 — nonce 가 든 응답이 캐시되면 같은 nonce 가 여러 사용자에게. XSS 표면 확대. 인증 페이지는 캐시 비활성.
하고픈 말
보안 헤더는 한 번 설정하면 그 이후의 코드가 자연스럽게 그 위에 쌓이는 토대. CSP strict-dynamic + HSTS preload + nosniff + Referrer-Policy + frame-ancestors 다섯 자리를 helmet · Caddy · next.config.js 의 일괄 설정으로 두고, securityheaders.com 으로 정기 점검. CORS 는 정확한 도메인 화이트리스트 + 적절한 preflight 응답.
Next
- (security 끝)
MDN CORS · MDN CSP · Google Web Fundamentals Strict CSP · RFC 6797 HSTS · Cross-Origin Isolation Guide · securityheaders.com · helmet 공식 · OWASP Secure Headers Project 를 참고합니다.