Next.js 安全最佳实践 🔒
🎯 概述
安全是 Web 应用的基础。Next.js 提供了多种内置安全功能和最佳实践。
📦 环境变量
安全存储
bash
# .env.local (不要提交到 Git)
DATABASE_URL="postgresql://..."
JWT_SECRET="your-secret-key"
API_KEY="your-api-key"
# .env (可以提交)
NEXT_PUBLIC_API_URL="https://api.example.com"使用环境变量
tsx
// 服务端
const secret = process.env.JWT_SECRET
// 客户端 (必须以 NEXT_PUBLIC_ 开头)
const apiUrl = process.env.NEXT_PUBLIC_API_URL🚀 CSRF 保护
使用 CSRF Token
tsx
// lib/csrf.ts
import { randomBytes } from 'crypto'
export function generateToken() {
return randomBytes(32).toString('hex')
}
export function verifyToken(token: string, storedToken: string) {
return token === storedToken
}
// app/api/form/route.ts
import { cookies } from 'next/headers'
import { verifyToken } from '@/lib/csrf'
export async function POST(request: Request) {
const token = request.headers.get('X-CSRF-Token')
const storedToken = cookies().get('csrf-token')?.value
if (!token || !verifyToken(token, storedToken)) {
return Response.json({ error: 'Invalid CSRF token' }, { status: 403 })
}
// 处理请求
}📝 XSS 防护
避免 dangerouslySetInnerHTML
tsx
// ❌ 不安全
<div dangerouslySetInnerHTML={{ __html: userInput }} />
// ✅ 安全
<div>{userInput}</div>
// ✅ 如果必须使用,先清理
import DOMPurify from 'isomorphic-dompurify'
<div dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(userInput)
}} />Content Security Policy
tsx
// middleware.ts
import { NextResponse } from 'next/server'
export function middleware(request: Request) {
const response = NextResponse.next()
response.headers.set(
'Content-Security-Policy',
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';"
)
return response
}🎨 SQL 注入防护
使用参数化查询
tsx
// ❌ 不安全
const query = `SELECT * FROM users WHERE email = '${email}'`
// ✅ 安全 (Prisma)
const user = await prisma.user.findUnique({
where: { email }
})
// ✅ 安全 (原生 SQL)
const user = await db.query(
'SELECT * FROM users WHERE email = $1',
[email]
)📊 认证和授权
JWT 验证
tsx
// lib/auth.ts
import jwt from 'jsonwebtoken'
export function verifyToken(token: string) {
try {
return jwt.verify(token, process.env.JWT_SECRET!)
} catch {
return null
}
}
// middleware.ts
export function middleware(request: Request) {
const token = request.headers.get('Authorization')?.split(' ')[1]
if (!token || !verifyToken(token)) {
return Response.json({ error: 'Unauthorized' }, { status: 401 })
}
return NextResponse.next()
}权限检查
tsx
// lib/permissions.ts
export function canEditPost(userId: string, post: Post) {
return post.authorId === userId
}
// app/api/posts/[id]/route.ts
export async function PUT(request: Request, { params }) {
const user = await getCurrentUser(request)
const post = await getPost(params.id)
if (!canEditPost(user.id, post)) {
return Response.json({ error: 'Forbidden' }, { status: 403 })
}
// 更新文章
}📚 最佳实践
1. 速率限制
tsx
// lib/rate-limit.ts
import { LRUCache } from 'lru-cache'
const rateLimit = new LRUCache({
max: 500,
ttl: 60000, // 1分钟
})
export function checkRateLimit(ip: string) {
const count = (rateLimit.get(ip) as number) || 0
if (count > 10) {
return false
}
rateLimit.set(ip, count + 1)
return true
}2. 输入验证
tsx
import { z } from 'zod'
const schema = z.object({
email: z.string().email(),
password: z.string().min(8),
})
export async function POST(request: Request) {
const body = await request.json()
try {
const data = schema.parse(body)
// 处理有效数据
} catch (error) {
return Response.json({ error: 'Invalid input' }, { status: 400 })
}
}3. 安全头部
tsx
// next.config.js
module.exports = {
async headers() {
return [
{
source: '/:path*',
headers: [
{
key: 'X-Frame-Options',
value: 'DENY',
},
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin',
},
],
},
]
},
}4. HTTPS
tsx
// middleware.ts
export function middleware(request: Request) {
if (
process.env.NODE_ENV === 'production' &&
request.headers.get('x-forwarded-proto') !== 'https'
) {
return NextResponse.redirect(
`https://${request.headers.get('host')}${request.nextUrl.pathname}`,
301
)
}
}🔗 相关资源
下一步:学习 Next.js 构建和部署。