프로덕션 필수 보안 헤더 설정하기
Lighthouse로 프로덕션 서비스를 감사하다 보면 Best Practices 항목에서 감점이 발생하는 경우가 많습니다. 대부분의 원인은 보안 헤더 미설정입니다.
최근 코드 리뷰 중 next.config.ts에 보안 헤더가 전혀 설정되어 있지 않은 프로젝트를 발견했습니다. 단순히 Lighthouse 점수 문제가 아니라, 클릭재킹(Clickjacking), XSS, MIME 스니핑 공격 등 실제 보안 위협에 노출될 수 있는 상태였습니다.
설정하는 김에 설정해야 할 6가지 보안 헤더와 next.config.ts에 적용하는 방법을 정리해서 업로드합니다.
설정이 필요한 프로덕션 필수 헤더
감사 대상 프로젝트의 next.config.ts에는 아래와 같은 프로덕션 필수 헤더가 하나도 설정되어 있지 않았습니다.
Content-Security-PolicyX-Frame-OptionsX-Content-Type-OptionsReferrer-PolicyPermissions-PolicyStrict-Transport-Security
설정이 없으면 아래와 같은 영향을 받습니다.
Lighthouse Best Practices 점수 하락
SEO 신뢰도 저하 (Google은 보안을 랭킹 신호로 사용)
실제 공격 벡터 노출 (클릭재킹, MIME 스니핑, 중간자 공격 등)
각각이 하는 역할
1. Content-Security-Policy (CSP)
브라우저가 로드할 수 있는 리소스(스크립트, 스타일, 이미지 등)의 출처를 제한합니다. XSS 공격의 피해를 최소화하는 가장 강력한 방어선입니다.
허용되지 않은 도메인에서 스크립트가 실행되지 않도록 막고, unsafe-inline이나 unsafe-eval을 제한함으로써 인라인 스크립트 인젝션을 차단합니다.
2. X-Frame-Options
페이지가 <iframe>, <frame>, <object> 안에 렌더링되는 것을 제어합니다. 클릭재킹(Clickjacking) 공격을 방어하는 핵심 헤더입니다.
DENY, SAMEORIGIN 두 값을 주로 사용하며, 대부분의 서비스는 SAMEORIGIN이면 충분합니다.
3. X-Content-Type-Options: nosniff
브라우저가 응답의 Content-Type을 임의로 추측(sniffing)하지 않도록 강제합니다. 예를 들어 공격자가 이미지 파일로 위장한 악성 스크립트를 업로드했을 때, 브라우저가 이를 스크립트로 해석해 실행하는 것을 막아줍니다.
값은 nosniff 하나뿐이므로 별 고민 없이 무조건 설정해야 하는 헤더입니다.
4. Referrer-Policy
외부 링크를 따라갈 때 Referer 헤더에 어떤 정보까지 포함시킬지 제어합니다. URL에 민감한 정보(토큰, 사용자 ID 등)가 포함된 경우 이것이 외부 사이트로 새어 나가는 것을 방지합니다.
일반적으로 strict-origin-when-cross-origin이 권장됩니다. 같은 출처에는 전체 URL을, 다른 출처에는 오리진(스킴+호스트)만 전달합니다.
5. Permissions-Policy
카메라, 마이크, 지오로케이션, 결제 API 등 브라우저 기능에 대한 접근 권한을 세밀하게 제어합니다. 과거의 Feature-Policy를 대체하는 최신 표준입니다.
사용하지 않는 기능은 명시적으로 차단해두는 것이 좋은 보안 습관입니다.
6. Strict-Transport-Security (HSTS)
브라우저에게 "이 사이트는 앞으로 무조건 HTTPS로만 접속해라"라고 지시합니다. 중간자 공격(MITM)이나 SSL 스트립 공격을 방어합니다.
주의: 한 번 설정하면 max-age 기간 동안 브라우저에 캐시되므로, HTTPS가 완전히 준비된 상태에서만 적용해야 합니다.
next.config.ts에 적용하기 (Sample Code)
import type { NextConfig } from "next";
const securityHeaders = [
{
key: "Content-Security-Policy",
value: [
"default-src 'self'",
"script-src 'self' 'unsafe-inline' 'unsafe-eval'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"font-src 'self' data:",
"connect-src 'self' https:",
"frame-ancestors 'none'",
].join("; "),
},
{
key: "X-Frame-Options",
value: "SAMEORIGIN",
},
{
key: "X-Content-Type-Options",
value: "nosniff",
},
{
key: "Referrer-Policy",
value: "strict-origin-when-cross-origin",
},
{
key: "Permissions-Policy",
value: "camera=(), microphone=(), geolocation=(), interest-cohort=()",
},
{
key: "Strict-Transport-Security",
value: "max-age=63072000; includeSubDomains; preload",
},
];
const nextConfig: NextConfig = {
async headers() {
return [
{
source: "/:path*",
headers: securityHeaders,
},
];
},
};
export default nextConfig;검증 방법
설정 후에는 반드시 아래 도구로 검증합니다.
securityheaders.com: 배포된 URL을 입력하면 헤더별로 A~F 등급을 매겨줍니다.
Chrome DevTools → Network 탭: 응답 헤더를 직접 확인할 수 있습니다.
Lighthouse: Best Practices 항목에서 개선된 점수를 확인합니다.
Mozilla Observatory: 종합적인 보안 평가를 제공합니다.
간단한 코드 몇줄로 서비스 공격을 크게 줄일 수 있기 때문에 권장드립니다.