Skip to main content

内容安全策略 (CSP)

内容安全策略(CSP)对于保护 Next.js 应用免受各种安全威胁至关重要,如跨站脚本攻击 (XSS)、点击劫持和其他代码注入攻击。

通过使用 CSP,开发者可以指定允许的内容来源,包括脚本、样式表、图片、字体、对象、媒体(音频、视频)、iframe 等的来源。

例子

Nonces

Nonce 是一个为一次性使用创建的唯一随机字符串。它与 CSP 一起使用,以选择性地允许某些内联脚本或样式执行,绕过严格的 CSP 指令。

为什么使用 nonce?

尽管 CSP 旨在阻止恶意脚本,但在某些合法情况下内联脚本是必需的。在这些情况下,nonces 提供了一种允许这些脚本执行的方法,只要它们具有正确的 nonce。

添加带有 Middleware 的 nonce

Middleware 允许您在页面渲染之前添加头信息并生成 nonce。

每次查看页面时,应该生成一个新的 nonce。这意味着您必须使用动态渲染来添加 nonce

例如:

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;
`;
// Replace newline characters and spaces
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;
`;
// Replace newline characters and spaces
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来筛选中间件只在特定路径上运行。

我们建议忽略next/link的预取请求和不需要 CSP 头的静态资源。

export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
*/
{
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: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
*/
{
source: "/((?!api|_next/static|_next/image|favicon.ico).*)",
missing: [
{ type: "header", key: "next-router-prefetch" },
{ type: "header", key: "purpose", value: "prefetch" },
],
},
],
};

读取 nonce

您现在可以使用headers服务器组件 中读取 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}
/>
);
}
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}
/>
);
}

无 Nonces

对于不需要 nonce 的应用程序,您可以直接在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+来正确处理和应用 nonce。