Skip to main content

内容安全政策

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

¥Content Security Policy (CSP) is important to guard your Next.js application against various security threats such as cross-site scripting (XSS), clickjacking, and other code injection attacks.

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

¥By using CSP, developers can specify which origins are permissible for content sources, scripts, stylesheets, images, fonts, objects, media (audio, video), iframes, and more.

Examples

随机数

¥Nonces

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

¥A nonce is a unique, random string of characters created for a one-time use. It is used in conjunction with CSP to selectively allow certain inline scripts or styles to execute, bypassing strict CSP directives.

为什么要使用随机数?

¥Why use a nonce?

尽管 CSP 旨在阻止恶意脚本,但在某些合法情况下需要内联脚本。在这种情况下,随机数提供了一种允许这些脚本执行的方法(如果它们具有正确的随机数)。

¥Even though CSPs are designed to block malicious scripts, there are legitimate scenarios where inline scripts are necessary. In such cases, nonces offer a way to allow these scripts to execute if they have the correct nonce.

使用中间件添加随机数

¥Adding a nonce with Middleware

中间件 使你能够在页面渲染之前添加标头并生成随机数。

¥Middleware enables you to add headers and generate nonces before the page renders.

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

¥Every time a page is viewed, a fresh nonce should be generated. This means that you must use dynamic rendering to add nonces.

例如:

¥For example:

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 过滤中间件以在特定路径上运行。

¥By default, Middleware runs on all requests. You can filter Middleware to run on specific paths using a matcher.

我们建议忽略匹配的预取(来自 next/link)和不需要 CSP 标头的静态资源。

¥We recommend ignoring matching prefetches (from next/link) and static assets that don't need the CSP header.

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' },
],
},
],
}

读取随机数

¥Reading the nonce

你现在可以使用 headers服务器组件 读取随机数:

¥You can now read the nonce from a Server Component using headers:

import { headers } from 'next/headers'
import Script from 'next/script'

export default async function Page() {
const nonce = (await 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 async function Page() {
const nonce = (await headers()).get('x-nonce')

return (
<Script
src="https://www.googletagmanager.com/gtag/js"
strategy="afterInteractive"
nonce={nonce}
/>
)
}

没有随机数

¥Without Nonces

对于不需要随机数的应用,你可以直接在 next.config.js 文件中设置 CSP 标头:

¥For applications that do not require nonces, you can set the CSP header directly in your next.config.js file:

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, ''),
},
],
},
]
},
}

版本历史

¥Version History

我们建议使用 Next.js 的 v13.4.20+ 来正确处理和应用随机数。

¥We recommend using v13.4.20+ of Next.js to properly handle and apply nonces.