内容安全策略 (Content Security Policy)

内容安全策略 (CSP) 对于防范跨站脚本 (XSS)、点击劫持和其他代码注入攻击等安全威胁至关重要。

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

示例

Nonce(随机数)

Nonce 是一个一次性使用的唯一随机字符串。它与 CSP 配合使用,可以绕过严格的 CSP 指令,选择性地允许特定内联脚本或样式执行。

为何使用 Nonce?

尽管 CSP 旨在阻止恶意脚本,但在某些合法场景中仍需使用内联脚本。此时,nonce 提供了一种验证机制——只有携带正确 nonce 的脚本才能执行。

通过中间件添加 Nonce

中间件 允许您在页面渲染前添加头部信息并生成 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';
    block-all-mixed-content;
    upgrade-insecure-requests;
`

  const requestHeaders = new Headers(request.headers)
  requestHeaders.set('x-nonce', nonce)
  requestHeaders.set(
    'Content-Security-Policy',
    // 替换换行符和多余空格
    cspHeader.replace(/\s{2,}/g, ' ').trim()
  )

  return NextResponse.next({
    headers: requestHeaders,
    request: {
      headers: requestHeaders,
    },
  })
}

默认情况下,中间件会处理所有请求。您可以通过 matcher 配置使其仅针对特定路径生效。

建议忽略来自 next/link 的预取请求以及不需要 CSP 头部的静态资源:

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

读取 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}
    />
  )
}

版本历史

建议使用 Next.js v13.4.20+ 版本以确保正确处理和应用 nonce。

On this page