Files
2026-07-13 21:36:17 +08:00

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 }
}

参考文档:https://nextjs.org/docs/app/guides/authentication