본문으로 건너뛰기
코드룸CODE ROOMTENMO ↗

프로덕션 필수 보안 헤더 설정하기

Lighthouse로 프로덕션 서비스를 감사하다 보면 Best Practices 항목에서 감점이 발생하는 경우가 많습니다. 대부분의 원인은 보안 헤더 미설정입니다.

최근 코드 리뷰 중 next.config.ts에 보안 헤더가 전혀 설정되어 있지 않은 프로젝트를 발견했습니다. 단순히 Lighthouse 점수 문제가 아니라, 클릭재킹(Clickjacking), XSS, MIME 스니핑 공격 등 실제 보안 위협에 노출될 수 있는 상태였습니다.

설정하는 김에 설정해야 할 6가지 보안 헤더와 next.config.ts에 적용하는 방법을 정리해서 업로드합니다.

설정이 필요한 프로덕션 필수 헤더

감사 대상 프로젝트의 next.config.ts에는 아래와 같은 프로덕션 필수 헤더가 하나도 설정되어 있지 않았습니다.

  • Content-Security-Policy

  • X-Frame-Options

  • X-Content-Type-Options

  • Referrer-Policy

  • Permissions-Policy

  • Strict-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: 종합적인 보안 평가를 제공합니다.

간단한 코드 몇줄로 서비스 공격을 크게 줄일 수 있기 때문에 권장드립니다.

블로그 목록으로