Skip to content

内容安全政策

¥Content Security Policy

内容安全策略 (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 可以阻止内联脚本和外部脚本以防止攻击。nonce 值允许你安全地允许特定脚本运行 - 前提是它们包含匹配的 nonce 值。

¥CSP can block both inline and external scripts to prevent attacks. A nonce lets you safely allow specific scripts to run—only if they include the matching nonce value.

如果攻击者想要将脚本加载到你的页面中,他们需要猜测 nonce 值。这就是为什么 nonce 必须不可预测且对于每个请求都是唯一的。

¥If an attacker wanted to load a script into your page, they'd need to guess the nonce value. That's why the nonce must be unpredictable and unique for every request.

使用中间件添加随机数

¥Adding a nonce with Middleware

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

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

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

¥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:

ts
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
}

默认情况下,中间件在所有请求上运行。你可以使用 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.

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

Next.js 中 nonces 的工作原理

¥How nonces work in Next.js

要使用 nonce,你的页面必须动态渲染。这是因为 Next.js 会在服务器端渲染期间根据请求中的 CSP 标头应用 nonce。静态页面在构建时生成,此时不存在请求或响应标头,因此无法注入 nonce。

¥To use a nonce, your page must be dynamically rendered. This is because Next.js applies nonces during server-side rendering, based on the CSP header present in the request. Static pages are generated at build time, when no request or response headers exist—so no nonce can be injected.

以下是动态渲染页面中 nonce 支持的工作原理:

¥Here’s how nonce support works in a dynamically rendered page:

  1. 中间件生成 nonce:你的中间件会为请求创建一个唯一的随机数,并将其添加到你的 Content-Security-Policy 标头中,并将其设置在自定义的 x-nonce 标头中。

    ¥Middleware generates a nonce: Your middleware creates a unique nonce for the request, adds it to your Content-Security-Policy header, and also sets it in a custom x-nonce header.

  2. Next.js 提取 nonce:在渲染过程中,Next.js 会解析 Content-Security-Policy 标头并使用 'nonce-{value}' 模式提取随机数 (nonce)。

    ¥Next.js extracts the nonce: During rendering, Next.js parses the Content-Security-Policy header and extracts the nonce using the 'nonce-{value}' pattern.

  3. 自动应用 nonce:Next.js 将 nonce 附加到:

    ¥Nonce is applied automatically: Next.js attaches the nonce to:

    • 框架脚本(React、Next.js 运行时)

      ¥Framework scripts (React, Next.js runtime)

    • 页面特定的 JavaScript 包

      ¥Page-specific JavaScript bundles

    • Next.js 生成的内联样式和脚本

      ¥Inline styles and scripts generated by Next.js

    • 任何使用 nonce 属性的 <Script> 组件

      ¥Any <Script> components using the nonce prop

由于这种自动行为,你无需手动向每个标签添加 nonce。

¥Because of this automatic behavior, you don’t need to manually add a nonce to each tag.

强制动态渲染

¥Forcing dynamic rendering

如果你使用的是 nonce,则可能需要明确选择将页面设置为动态渲染:

¥If you're using nonces, you may need to explicitly opt pages into dynamic rendering:

tsx
import { connection } from 'next/server'

export default async function Page() {
  // wait for an incoming request to render this page
  await connection()
  // Your page content
}

读取随机数

¥Reading the nonce

你可以使用 headers服务器组件 读取 nonce:

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

tsx
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}
    />
  )
}

使用 CSP 的静态渲染与动态渲染

¥Static vs Dynamic Rendering with CSP

使用 nonces 对 Next.js 应用的渲染方式具有重要影响:

¥Using nonces has important implications for how your Next.js application renders:

动态渲染要求

¥Dynamic Rendering Requirement

在 CSP 中使用 nonce 时,所有页面都必须动态渲染。这意味着:

¥When you use nonces in your CSP, all pages must be dynamically rendered. This means:

  • 页面将成功构建,但如果未正确配置动态渲染,可能会出现运行时错误

    ¥Pages will build successfully but may encounter runtime errors if not properly configured for dynamic rendering

  • 每个请求都会生成一个带有新随机数的新页面

    ¥Each request generates a fresh page with a new nonce

  • 静态优化和增量静态再生 (ISR) 已禁用

    ¥Static optimization and Incremental Static Regeneration (ISR) are disabled

  • 未经额外配置,CDN 无法缓存页面

    ¥Pages cannot be cached by CDNs without additional configuration

  • 部分预渲染 (PPR) 与基于 nonce 的 CSP 不兼容,因为静态 Shell 脚本无法访问 nonce

    ¥Partial Prerendering (PPR) is incompatible with nonce-based CSP since static shell scripts won't have access to the nonce

性能影响

¥Performance Implications

从静态渲染到动态渲染的转变会影响性能:

¥The shift from static to dynamic rendering affects performance:

  • 初始页面加载速度较慢:每次请求都必须生成页面

    ¥Slower initial page loads: Pages must be generated on each request

  • 增加服务器负载:每个请求都需要服务器端渲染

    ¥Increased server load: Every request requires server-side rendering

  • 无 CDN 缓存:动态页面默认无法在边缘缓存

    ¥No CDN caching: Dynamic pages cannot be cached at the edge by default

  • 更高的托管成本:动态渲染需要更多服务器资源

    ¥Higher hosting costs: More server resources needed for dynamic rendering

何时使用 nonce

¥When to use nonces

在以下情况下考虑使用 nonces:

¥Consider nonces when:

  • 你有严格的安全要求,禁止使用 'unsafe-inline'

    ¥You have strict security requirements that prohibit 'unsafe-inline'

  • 你的应用处理敏感数据

    ¥Your application handles sensitive data

  • 你需要允许特定的内联脚本,同时阻止其他脚本

    ¥You need to allow specific inline scripts while blocking others

  • 合规性要求强制执行严格的内容安全策略 (CSP)

    ¥Compliance requirements mandate strict CSP

没有随机数

¥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:

子资源完整性(实验性)

¥Subresource Integrity (Experimental)

作为 nonce 的替代方案,Next.js 使用子资源完整性 (SRI) 实验性地支持基于哈希值的 CSP。这种方法允许你在保持严格内容安全策略 (CSP) 的同时保持静态生成。

¥As an alternative to nonces, Next.js offers experimental support for hash-based CSP using Subresource Integrity (SRI). This approach allows you to maintain static generation while still having a strict CSP.

需要了解:此功能尚处于实验阶段,仅在 App Router 应用中通过 webpack 打包器提供。

¥Good to know: This feature is experimental and only available with webpack bundler in App Router applications.

SRI 的工作原理

¥How SRI works

SRI 不使用 nonce,而是在构建时生成 JavaScript 文件的加密哈希值。这些哈希值作为 integrity 属性添加到脚本标签中,允许浏览器验证文件在传输过程中未被修改。

¥Instead of using nonces, SRI generates cryptographic hashes of your JavaScript files at build time. These hashes are added as integrity attributes to script tags, allowing browsers to verify that files haven't been modified during transit.

启用 SRI

¥Enabling SRI

将实验性的 SRI 配置添加到你的 next.config.js

¥Add the experimental SRI configuration to your next.config.js:

使用 SRI 的 CSP 配置

¥CSP configuration with SRI

启用 SRI 后,你可以继续使用现有的 CSP 策略。SRI 通过将 integrity 属性添加到你的资源中来独立工作:

¥When SRI is enabled, you can continue using your existing CSP policies. SRI works independently by adding integrity attributes to your assets:

需要了解:对于动态渲染场景,你仍然可以根据需要使用中间件生成 nonce,结合 SRI 完整性属性和基于 nonce 的 CSP 方法。

¥Good to know: For dynamic rendering scenarios, you can still generate nonces with middleware if needed, combining both SRI integrity attributes and nonce-based CSP approaches.

SRI 相对于 nonces 的优势

¥Benefits of SRI over nonces

  • 静态资源生成:页面可以静态生成和缓存

    ¥Static generation: Pages can be statically generated and cached

  • CDN 兼容性:静态页面可与 CDN 缓存配合使用

    ¥CDN compatibility: Static pages work with CDN caching

  • 更好的性能:每个请求无需服务器端渲染

    ¥Better performance: No server-side rendering required for each request

  • 构建时安全性:哈希值在构建时生成,以确保完整性

    ¥Build-time security: Hashes are generated at build time, ensuring integrity

SRI 的局限性

¥Limitations of SRI

  • 实验性:功能可能会更改或被移除

    ¥Experimental: Feature may change or be removed

  • 仅限 Webpack:Turbopack 不可用

    ¥Webpack only: Not available with Turbopack

  • 仅限应用路由:Pages Router 不支持

    ¥App Router only: Not supported in Pages Router

  • 仅限构建时:无法处理动态生成的脚本

    ¥Build-time only: Cannot handle dynamically generated scripts

开发与生产注意事项

¥Development vs Production Considerations

CSP 的实现在开发环境和生产环境中有所不同:

¥CSP implementation differs between development and production environments:

开发环境

¥Development Environment

在开发过程中,你需要启用 'unsafe-eval' 以支持提供额外调试信息的 API:

¥In development, you will need to enable 'unsafe-eval' to support APIs that provide additional debugging information:

ts
export function middleware(request: NextRequest) {
  const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
  const isDev = process.env.NODE_ENV === 'development'

  const cspHeader = `
    default-src 'self';
    script-src 'self' 'nonce-${nonce}' 'strict-dynamic' ${isDev ? "'unsafe-eval'" : ''};
    style-src 'self' 'nonce-${nonce}' ${isDev ? "'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;
`

  // Rest of middleware implementation
}

生产部署

¥Production Deployment

生产环境中的常见问题:

¥Common issues in production:

  • 不应用 nonce:确保你的中间件在所有必要的路由上运行

    ¥Nonce not applied: Ensure your middleware runs on all necessary routes

  • 静态资源被阻止:验证你的 CSP 是否允许 Next.js 静态资源

    ¥Static assets blocked: Verify your CSP allows Next.js static assets

  • 第三方脚本:将必要的域名添加到你的 CSP 策略

    ¥Third-party scripts: Add necessary domains to your CSP policy

故障排除

¥Troubleshooting

第三方脚本

¥Third-party Scripts

在 CSP 中使用第三方脚本时:

¥When using third-party scripts with CSP:

tsx
import { GoogleTagManager } from '@next/third-parties/google'
import { headers } from 'next/headers'

export default async function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  const nonce = (await headers()).get('x-nonce')

  return (
    <html lang="en">
      <body>
        {children}
        <GoogleTagManager gtmId="GTM-XYZ" nonce={nonce} />
      </body>
    </html>
  )
}

更新你的 CSP 以允许第三方域名:

¥Update your CSP to allow third-party domains:

ts
const cspHeader = `
  default-src 'self';
  script-src 'self' 'nonce-${nonce}' 'strict-dynamic' https://www.googletagmanager.com;
  connect-src 'self' https://www.google-analytics.com;
  img-src 'self' data: https://www.google-analytics.com;
`

常见的 CSP 违规行为

¥Common CSP Violations

  1. 内联样式:使用支持 nonce 的 CSS-in-JS 库或将样式移动到外部文件

    ¥Inline styles: Use CSS-in-JS libraries that support nonces or move styles to external files

  2. 动态导入:确保你的 script-src 策略允许动态导入

    ¥Dynamic imports: Ensure dynamic imports are allowed in your script-src policy

  3. WebAssembly:如果使用 WebAssembly,请添加 'wasm-unsafe-eval'

    ¥WebAssembly: Add 'wasm-unsafe-eval' if using WebAssembly

  4. 服务工作者:为 Service Worker 脚本添加适当的策略

    ¥Service workers: Add appropriate policies for service worker scripts

版本历史

¥Version History

版本更改
v14.0.0添加了基于哈希值的 CSP 的实验性 SRI 支持
v13.4.20建议用于正确的 nonce 处理和 CSP 标头解析。