콘텐츠 보안 정책
콘텐츠 보안 정책(CSP)은 크로스 사이트 스크립팅(XSS), 클릭재킹 및 기타 코드 주입 공격과 같은 다양한 보안 위협으로부터 Next.js 애플리케이션을 보호하는 데 중요합니다.
CSP를 사용하면 개발자는 콘텐츠 소스, 스크립트, 스타일시트, 이미지, 폰트, 객체, 미디어(오디오, 비디오), iframe 등에 대해 허용되는 출처를 지정할 수 있습니다.
예시
논스 (Nounce)
논스는 일회성 사용을 위해 생성된 고유하고 무작위한 문자열입니다. CSP와 함께 사용되어 엄격한 CSP 지시문을 우회하면서 특정 인라인 스크립트나 스타일이 실행되도록 선택적으로 허용합니다.
왜 논스(Nounce)를 사용하나요?
CSP는 악성 스크립트를 차단하도록 설계되었지만, 인라인 스크립트가 필요한 정당한 시나리오가 있습니다. 이러한 경우 논스는 올바른 논스가 있는 이러한 스크립트가 실행되도록 허용하는 방법을 제공합니다.
미들웨어로 논스(Nounce) 추가하기
미들웨어를 사용하면 페이지가 렌더링되기 전에 헤더를 추가하고 논스를 생성할 수 있습니다.
페이지를 볼 때마다 새로운 논스가 생성되어야 합니다. 이는 논스를 추가하기 위해 동적 렌더링을 사용해야 한다는 것을 의미합니다.
예를 들어:
import { NextRequest, NextResponse } from "next/server";
export function middleware(request: NextRequest) {
const nonce = Buffer.from(crypto.randomUUID()).toString("base64");
const cspHeader = `
default-src 'self';
script-src 'self' 'nonce-${nonce}' 'strict-dynamic';
style-src 'self' 'nonce-${nonce}';
img-src 'self' blob: data:;
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
`;
// 줄바꿈 문자와 공백 제거
const contentSecurityPolicyHeaderValue = cspHeader
.replace(/\s{2,}/g, " ")
.trim();
const requestHeaders = new Headers(request.headers);
requestHeaders.set("x-nonce", nonce);
requestHeaders.set(
"Content-Security-Policy",
contentSecurityPolicyHeaderValue
);
const response = NextResponse.next({
request: {
headers: requestHeaders,
},
});
response.headers.set(
"Content-Security-Policy",
contentSecurityPolicyHeaderValue
);
return response;
}
import { NextResponse } from "next/server";
export function middleware(request) {
const nonce = Buffer.from(crypto.randomUUID()).toString("base64");
const cspHeader = `
default-src 'self';
script-src 'self' 'nonce-${nonce}' 'strict-dynamic';
style-src 'self' 'nonce-${nonce}';
img-src 'self' blob: data:;
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
`;
// 줄바꿈 문자와 공백 제거
const contentSecurityPolicyHeaderValue = cspHeader
.replace(/\s{2,}/g, " ")
.trim();
const requestHeaders = new Headers(request.headers);
requestHeaders.set("x-nonce", nonce);
requestHeaders.set(
"Content-Security-Policy",
contentSecurityPolicyHeaderValue
);
const response = NextResponse.next({
request: {
headers: requestHeaders,
},
});
response.headers.set(
"Content-Security-Policy",
contentSecurityPolicyHeaderValue
);
return response;
}
기본적으로 미들웨어는 모든 요청에서 실행됩니다. matcher
를 사용하여 특정 경로에서 미들웨어를 실행하도록 필터링할 수 있습니다.
CSP 헤더가 필요하지 않은 프리페치(next/link
에서)와 정적 자산에 대한 매칭을 무시하는 것이 좋습니다.
export const config = {
matcher: [
/*
* 다음으로 시작하는 요청 경로를 제외한 모든 요청 경로와 일치:
* - api (API 라우트)
* - _next/static (정적 파일)
* - _next/image (이미지 최적화 파일)
* - favicon.ico (파비콘 파일)
*/
{
source: "/((?!api|_next/static|_next/image|favicon.ico).*)",
missing: [
{ type: "header", key: "next-router-prefetch" },
{ type: "header", key: "purpose", value: "prefetch" },
],
},
],
};
export const config = {
matcher: [
/*
* 다음으로 시작하는 요청 경로를 제외한 모든 요청 경로와 일치:
* - api (API 라우트)
* - _next/static (정적 파일)
* - _next/image (이미지 최적화 파일)
* - favicon.ico (파비콘 파일)
*/
{
source: "/((?!api|_next/static|_next/image|favicon.ico).*)",
missing: [
{ type: "header", key: "next-router-prefetch" },
{ type: "header", key: "purpose", value: "prefetch" },
],
},
],
};
논스(Nounce) 읽기
이제 headers
를 사용하여 서버 컴포넌트에서 논스를 읽을 수 있습니다:
import { headers } from "next/headers";
import Script from "next/script";
export default function Page() {
const nonce = headers().get("x-nonce");
return (
<Script
src="https://www.googletagmanager.com/gtag/js"
strategy="afterInteractive"
nonce={nonce}
/>
);
}
import { headers } from "next/headers";
import Script from "next/script";
export default function Page() {
const nonce = headers().get("x-nonce");
return (
<Script
src="https://www.googletagmanager.com/gtag/js"
strategy="afterInteractive"
nonce={nonce}
/>
);
}
논스(Nounce) 없이
논스가 필요하지 않은 애플리케이션의 경우 next.config.js
파일에서 직접 CSP 헤더를 설정할 수 있습니다:
const cspHeader = `
default-src 'self';
script-src 'self' 'unsafe-eval' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
img-src 'self' blob: data:;
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
`;
module.exports = {
async headers() {
return [
{
source: "/(.*)",
headers: [
{
key: "Content-Security-Policy",
value: cspHeader.replace(/\n/g, ""),
},
],
},
];
},
};
버전 기록
논스를 적절히 처리하고 적용하려면 Next.js의 v13.4.20+
버전을 사용하는 것이 좋습니다.