项目文件夹

文件
2026-07-13 21:36:17 +08:00

97 行
2.7 KiB
Markdown

---
title: 像 API 路由一样对 Server Actions 进行身份验证
impact: CRITICAL
impactDescription: 防止对服务器端修改操作进行未授权访问
tags: 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 端点,并验证用户是否有权执行该修改操作。"
**错误示例(无身份验证检查):**
```typescript
'use server'
export async function deleteUser(userId: string) {
// 任何人都可以调用!没有身份验证检查
await db.user.delete({ where: { id: userId } })
return { success: true }
}
```
**正确示例(在 Action 内部进行身份验证):**
```typescript
'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 }
}
```
**带输入验证的示例:**
```typescript
'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 }
}
```
参考文档:[https://nextjs.org/docs/app/guides/authentication](https://nextjs.org/docs/app/guides/authentication)