HTTP 헤더란

HTTP 헤더는 클라이언트와 서버 간 HTTP 요청/응답에 포함되는 메타데이터입니다. 요청 헤더는 클라이언트가 서버에 전송하는 정보(브라우저 정보, 허용 콘텐츠 타입 등)를 담고, 응답 헤더는 서버가 클라이언트에게 전달하는 지시사항(캐싱 정책, 보안 정책, 콘텐츠 타입 등)을 포함합니다.

헤더는 이름: 값 형식의 텍스트 쌍으로 구성됩니다. 웹 보안의 상당 부분이 올바른 응답 헤더 설정으로 달성됩니다. HSTS, CSP, X-Frame-Options 등의 보안 헤더를 누락하면 클릭재킹, XSS, 중간자 공격 등에 취약해집니다.

필수 보안 헤더

HSTS (Strict-Transport-Security)

HSTS는 브라우저에 해당 사이트에 HTTPS로만 접속하도록 지시합니다. 한 번 설정되면 max-age 기간 동안 브라우저가 자동으로 HTTP → HTTPS로 업그레이드합니다. 이는 SSL 스트리핑 공격을 방지합니다.

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

includeSubDomains는 모든 서브도메인에도 적용하며, preload는 브라우저 내장 HSTS 목록에 포함 신청이 가능함을 의미합니다.

CSP (Content-Security-Policy)

CSP는 페이지에서 로드할 수 있는 리소스의 출처를 제한합니다. XSS 공격 방지에 가장 효과적인 헤더입니다.

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{random}'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https://fonts.googleapis.com

CSP는 강력하지만 설정이 복잡합니다. 기존 사이트에 점진적으로 도입할 때는 Content-Security-Policy-Report-Only 헤더로 위반 사항만 리포트하고 차단하지 않는 모드로 시작하세요.

X-Frame-Options

클릭재킹 공격을 방지합니다. 페이지가 <iframe>으로 로드되는 것을 제한합니다.

X-Frame-Options: SAMEORIGIN

DENY는 모든 임베딩 차단, SAMEORIGIN은 동일 도메인만 허용합니다. 최신 CSP의 frame-ancestors 디렉티브가 더 세밀한 제어를 지원하지만, 하위 호환성을 위해 두 헤더를 함께 설정하는 것이 좋습니다.

X-Content-Type-Options

X-Content-Type-Options: nosniff

브라우저가 Content-Type을 무시하고 파일 내용을 추측(MIME sniffing)하는 것을 방지합니다. 이미지로 위장한 악성 스크립트 실행을 막을 수 있습니다.

Referrer-Policy

Referrer-Policy: strict-origin-when-cross-origin

다른 사이트로 이동할 때 전달하는 Referrer 정보를 제어합니다. strict-origin-when-cross-origin은 동일 출처 요청에는 전체 URL을, 교차 출처 요청에는 도메인만 전송하며, HTTP → HTTPS 요청에는 Referrer를 생략합니다.

CORS (Cross-Origin Resource Sharing)

CORS는 웹 페이지가 자신의 출처(도메인, 프로토콜, 포트)와 다른 출처의 리소스에 접근할 수 있도록 허용하는 메커니즘입니다. 브라우저의 동일 출처 정책(Same-Origin Policy)을 선택적으로 완화합니다.

# 특정 출처만 허용 (권장)
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization

# 인증 정보 포함 요청 허용 (주의: Allow-Origin에 * 사용 불가)
Access-Control-Allow-Credentials: true

CORS 오류의 가장 흔한 원인은 Access-Control-Allow-Origin 헤더 누락 또는 와일드카드(*)와 credentials 혼용입니다. 인증 정보를 포함하는 요청에서는 와일드카드를 사용할 수 없으며, 반드시 특정 출처를 명시해야 합니다.

캐시 헤더 전략

Cache-Control은 캐싱 동작을 제어하는 핵심 헤더입니다. 잘못된 설정은 오래된 컨텐츠 제공 또는 불필요한 서버 요청 증가로 이어집니다.

# 정적 자산 (JS, CSS, 이미지) - 장기 캐시
Cache-Control: public, max-age=31536000, immutable

# HTML 파일 - 항상 재검증
Cache-Control: no-cache

# API 응답 - 캐시 금지
Cache-Control: no-store, no-cache, must-revalidate

# 공용 CDN 허용, 1시간 캐시
Cache-Control: public, max-age=3600, s-maxage=86400

파일명에 해시를 포함(예: main.a3f2b1.js)하면 immutable과 함께 1년 캐시를 안전하게 사용할 수 있습니다. HTML은 항상 최신 파일을 받도록 no-cache로 설정하세요.

Nginx에서 보안 헤더 설정

server {
    # 보안 헤더
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

    # 정적 자산 캐시
    location ~* \.(js|css|png|jpg|gif|ico|woff2)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
}

자주 묻는 질문

HSTS를 설정하면 HTTP 사이트는 어떻게 되나요?

HSTS가 브라우저에 저장된 이후에는 HTTP 요청을 브라우저가 자동으로 HTTPS로 변환합니다. 단, HTTPS가 실제로 작동하는 상태에서만 HSTS를 설정해야 합니다. 잘못 설정하면 max-age 기간 동안 사이트에 접속할 수 없게 될 수 있습니다.

CSP를 설정했는데 인라인 스크립트가 막히는 이유는 무엇인가요?

기본 CSP는 인라인 스크립트(<script> 태그 내 코드)를 차단합니다. 인라인 스크립트가 필요하다면 script-src 'unsafe-inline'을 추가하거나, 더 안전한 방법으로 nonce('nonce-xxx')를 사용하세요. nonce는 매 요청마다 랜덤 값을 생성하여 신뢰된 인라인 스크립트만 허용합니다.

Cloudflare를 사용하면 헤더를 별도로 설정해야 하나요?

Cloudflare는 일부 기본 보안 헤더를 추가하지만, CSP, HSTS 등 세밀한 설정은 직접 해야 합니다. Cloudflare의 Transform Rules를 사용하면 서버 설정 없이 헤더를 추가/수정할 수 있습니다. 다만 HSTS는 Cloudflare의 SSL/TLS 설정에서도 별도로 활성화하는 것을 권장합니다.

도구 활용

HTTP 응답 헤더를 분석하고 보안 점수를 확인하려면 HTTP 헤더 분석 도구를 사용하세요. 브라우저 개발자 도구에서 복사한 헤더를 붙여넣으면 즉시 분석됩니다.

광고 영역