2.7 KiB
2.7 KiB
title, impact, impactDescription, tags
| title | impact | impactDescription | tags |
|---|---|---|---|
| 像 API 路由一样对 Server Actions 进行身份验证 | CRITICAL | 防止对服务器端修改操作进行未授权访问 | server, server-actions, authentication, security, authorization |
像 API 路由一样对 Server Actions 进行身份验证
影响:严重(防止对服务器端修改操作进行未授权访问)
Server Actions(带有 "use server" 的函数)与 API 路由一样,会被暴露为公开端点。务必在每个 Server Action 内部 验证身份认证(authentication)和授权(authorization)——不要仅依赖中间件、布局守卫或页面级检查,因为 Server Actions 可以直接被调用。
Next.js 文档明确指出:"应将 Server Actions 的安全级别视同公开的 API 端点,并验证用户是否有权执行该修改操作。"
错误示例(无身份验证检查):
'use server'
export async function deleteUser(userId: string) {
// 任何人都可以调用!没有身份验证检查
await db.user.delete({ where: { id: userId } })
return { success: true }
}
正确示例(在 Action 内部进行身份验证):
'use server'
import { verifySession } from '@/lib/auth'
import { unauthorized } from '@/lib/errors'
export async function deleteUser(userId: string) {
// 始终在 Action 内部检查身份认证
const session = await verifySession()
if (!session) {
throw unauthorized('必须登录')
}
// 同时检查授权
if (session.user.role !== 'admin' && session.user.id !== userId) {
throw unauthorized('不能删除其他用户')
}
await db.user.delete({ where: { id: userId } })
return { success: true }
}
带输入验证的示例:
'use server'
import { verifySession } from '@/lib/auth'
import { z } from 'zod'
const updateProfileSchema = z.object({
userId: z.string().uuid(),
name: z.string().min(1).max(100),
email: z.string().email()
})
export async function updateProfile(data: unknown) {
// 先验证输入
const validated = updateProfileSchema.parse(data)
// 再进行身份认证
const session = await verifySession()
if (!session) {
throw new Error('未授权')
}
// 然后检查授权
if (session.user.id !== validated.userId) {
throw new Error('只能更新自己的个人资料')
}
// 最后执行修改操作
await db.user.update({
where: { id: validated.userId },
data: {
name: validated.name,
email: validated.email
}
})
return { success: true }
}