skillhub-088-vercel-react-best-practices
97 行
2.7 KiB
Markdown
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)
|