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 的工作流程
- Proxy 生成 nonce:每个请求生成唯一的随机字符串
- Next.js 提取 nonce:渲染时从请求头中读取
- 自动应用到脚本:框架脚本、页面 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 防护:
- Server Actions 只接受 POST:防止 GET 请求触发副作用
- Origin 检查:自动比对 Origin 和 Host 头
- 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
- 返回值是否只包含必要字段
安全是个持续的过程。定期审计代码,关注安全公告,才能保持应用的安全性。