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: 종합적인 보안 평가를 제공합니다.
간단한 코드 몇줄로 서비스 공격을 크게 줄일 수 있기 때문에 권장드립니다.
