首页 / Next.js 16 入门教程 / 安全与内容安全策略

Next.js 16 入门教程

安全与内容安全策略

本教程共 42 篇 · 第 40 篇 · 更新于 2026-07-30 · 约 8 分钟阅读

Next.jsNext.js 16 入门教程安全CSPCSRFXSS内容安全策略

40. 安全与内容安全策略

本节目标:学会配置 Content Security Policy,理解 Next.js 内置的安全防护机制,掌握数据安全的最佳实践。

Content Security Policy (CSP)

CSP 是一组 HTTP 头,告诉浏览器哪些资源可以加载和执行。它能有效防止 XSS、点击劫持等攻击。

基本配置(无 nonce)

不需要动态 nonce 的场景,可以直接在 next.config.ts 中设置:

// next.config.ts
import type { NextConfig } from 'next'

const cspHeader = `
  default-src 'self';
  script-src 'self' '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;
`

const nextConfig: NextConfig = {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'Content-Security-Policy',
            value: cspHeader.replace(/\n/g, ''),
          },
        ],
      },
    ]
  },
}

export default nextConfig

使用 Nonce 的严格 CSP

对安全要求更高的场景,用 nonce 机制:

// proxy.ts
import { NextRequest, NextResponse } from 'next/server'

export function proxy(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}';
    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
}

Nonce 的工作流程

  1. Proxy 生成 nonce:每个请求生成唯一的随机字符串
  2. Next.js 提取 nonce:渲染时从请求头中读取
  3. 自动应用到脚本:框架脚本、页面 JS 自动带上 nonce

注意:使用 nonce 的页面必须动态渲染,因为 nonce 是请求时生成的。

读取 Nonce

在 Server Component 中读取 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}
    />
  )
}

过滤预加载请求

CSP 不需要应用到预加载和静态资源:

export const config = {
  matcher: [
    {
      source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
      missing: [
        { type: 'header', key: 'next-router-prefetch' },
        { type: 'header', key: 'purpose', value: 'prefetch' },
      ],
    },
  ],
}

Subresource Integrity (SRI)

SRI 是 nonce 的替代方案,用哈希值验证资源完整性:

// next.config.ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  experimental: {
    sri: {
      algorithm: 'sha256',
    },
  },
}

export default nextConfig

SRI 的优势:

  • 页面可以静态生成
  • CDN 缓存友好
  • 不需要动态渲染

CSRF 防护

Next.js 内置了 CSRF 防护:

  1. Server Actions 只接受 POST:防止 GET 请求触发副作用
  2. Origin 检查:自动比对 Origin 和 Host 头
  3. SameSite Cookie:现代浏览器默认开启

如果需要自定义允许的源:

// next.config.ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  experimental: {
    serverActions: {
      allowedOrigins: ['my-proxy.com', '*.my-proxy.com'],
    },
  },
}

export default nextConfig

XSS 防护

自动转义

React 默认转义 JSX 中的变量,防止 XSS:

// 安全:内容会被转义
<div>{userInput}</div>

// 危险:直接渲染 HTML
<div dangerouslySetInnerHTML={{ __html: userInput }} />

Taint API

React 提供了 Taint API 防止敏感数据意外传给客户端:

// next.config.ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  experimental: {
    taint: true,
  },
}

export default nextConfig
import { experimental_taintObjectReference } from 'react'

// 标记敏感对象
experimental_taintObjectReference(
  '不要把这个对象传给客户端',
  sensitiveData
)

数据安全

数据访问层(DAL)

推荐创建专门的数据访问层,集中处理权限和数据过滤:

// data/auth.ts
import { cache } from 'react'
import { cookies } from 'next/headers'

export const getCurrentUser = cache(async () => {
  const cookieStore = await cookies()
  const token = cookieStore.get('AUTH_TOKEN')
  const decoded = await decryptAndValidate(token)
  return new User(decoded.id) // 只返回安全字段
})

服务端/客户端隔离

  • Server Components:可以安全访问环境变量、数据库
  • Client Components:不能访问敏感数据,必须遵循浏览器安全模型

server-only 包防止服务端代码泄露到客户端:

import 'server-only'

// 这个模块只能在服务端运行
export const dbSecret = process.env.DATABASE_URL

验证用户输入

永远不要信任客户端传来的数据:

// 错误:直接信任 URL 参数
export default async function Page({ searchParams }) {
  const isAdmin = (await searchParams).isAdmin
  if (isAdmin === 'true') {
    return <AdminPanel />
  }
}

// 正确:重新验证权限
export default async function Page() {
  const cookieStore = await cookies()
  const token = cookieStore.get('AUTH_TOKEN')
  const isAdmin = await verifyAdmin(token)

  if (isAdmin) {
    return <AdminPanel />
  }
}

Server Actions 安全

每个 Server Action 都是公开的 POST 端点。即使没在 UI 中调用,也可能被外部直接请求。

'use server'

export async function deletePost(postId: string) {
  const session = await auth()
  if (!session?.user) {
    throw new Error('未登录')
  }

  const post = await db.post.findUnique({ where: { id: postId } })

  // 检查资源所有权
  if (post.authorId !== session.user.id) {
    throw new Error('无权限')
  }

  await db.post.delete({ where: { id: postId } })
}

安全审计清单

  • CSP 是否配置
  • Server Actions 是否验证权限
  • 敏感数据是否过滤后才传给客户端
  • 环境变量是否只在服务端使用
  • 用户输入是否验证
  • 第三方脚本是否用 nonce
  • 返回值是否只包含必要字段

安全是个持续的过程。定期审计代码,关注安全公告,才能保持应用的安全性。