- 人工智能
- AI Agent
- 代码智能体
- Agent 编排
- CLI
- AI 应用
【免费下载链接】gsd-2
A powerful meta-prompting, context engineering and spec-driven development system that enables agents to work for long periods of time autonomously without losing track of the big picture
本指南围绕 gsd-2 仓库中内置的 Vercel React/Next.js 最佳实践技能(react-best-practices)的server-auth-actions规则展开,解决一个常见却危险的问题:Server Actions 被当作公开 HTTP 端点暴露,任何未认证用户都可以直接调用。读完本文,你将掌握"在 action 内部完成认证 + 授权 + 输入校验"的完整写法,并理解为什么 middleware、布局守卫和页面级检查都无法替代 action 内的第一道安全防线。
规则出处与定位
该规则位于仓库的 react-best-practices 技能包 下,具体文件为 rules/server-auth-actions.md。根据技能的 SKILL.md 与 metadata.json,这套指南由 Vercel Engineering 维护,共 57 条规则、8 大分类,按影响优先级排列:
| 优先级 | 分类 | 影响 | 前缀 |
|---|---|---|---|
| 1 | 消除 Waterfall | CRITICAL | async- |
| 2 | Bundle 体积优化 | CRITICAL | bundle- |
| 3 | 服务端性能 | HIGH | server- |
| 4 | 客户端数据获取 | MEDIUM-HIGH | client- |
| 5 | 重渲染优化 | MEDIUM | rerender- |
| 6 | 渲染性能 | MEDIUM | rendering- |
| 7 | JavaScript 性能 | LOW-MEDIUM | js- |
| 8 | 进阶模式 | LOW | advanced- |
server-auth-actions属于第 3 类"服务端性能(Server-Side Performance)",但其impact字段标注为CRITICAL,impactDescription为"prevents unauthorized access to server mutations"(防止未授权访问服务端写操作)——它是该分类中唯一的 CRITICAL 级规则,因为它保护的并非性能,而是数据安全本身。
核心事实:Server Actions 就是公开端点
Next.js 中凡是带有'use server'指令的函数(即 Server Actions),编译后都会被框架自动暴露为可供客户端 HTTP 调用的端点(endpoint)。这意味着:
- 任何知道 action 标识符的人都可以绕过 UI 直接发起调用,正如可以绕过页面直接 POST 到 API 路由一样;
- 前端页面上的按钮只是其中一个调用方,绝不是唯一调用方;
- 因此页面级检查、布局守卫、middleware 都只是"用户体验层"的拦截,不是安全边界——真正的安全边界必须落在 action 自身内部。
Next.js 官方文档对此有明确表述:"Treat Server Actions with the same security considerations as public-facing API endpoints, and verify if the user is allowed to perform a mutation."(应当像对待面向公众的 API 端点一样对待 Server Actions,并验证用户是否被允许执行该写操作。)这正是本规则的立论依据。
错误示范:没有任何认证检查的写操作
规则首先给出了一个典型的错误实现——deleteUser直接对数据库执行删除:
'use server' export async function deleteUser(userId: string) { // Anyone can call this! No auth check await db.user.delete({ where: { id: userId } }) return { success: true } }这段代码的问题一目了然:函数被导出为 Server Action 后,等同于一个无需任何凭证即可调用的公开接口。任何能够拿到 action 引用(例如通过网络请求分析、源码映射或对编译产物进行探测)的攻击者,都可以随意删除任意用户。规则对此的评语是"Anyone can call this!"——没有认证检查的 mutation 是最高危的安全缺陷。
正确示范:在 action 内部完成认证与授权
正确做法是把verifySession()的校验放进每个 action 内部,并且在认证通过后再做一层**授权(authorization)**判断——认证回答"你是谁",授权回答"你是否有权做这件事":
'use server' import { verifySession } from '@/lib/auth' import { unauthorized } from '@/lib/errors' export async function deleteUser(userId: string) { // Always check auth inside the action const session = await verifySession() if (!session) { throw unauthorized('Must be logged in') } // Check authorization too if (session.user.role !== 'admin' && session.user.id !== userId) { throw unauthorized('Cannot delete other users') } await db.user.delete({ where: { id: userId } }) return { success: true } }这段示例体现了三条关键实践:
- 认证前置:第一行就调用
verifySession(),未登录直接抛出unauthorized,绝不执行后续任何业务逻辑; - 授权细分:仅"已登录"不够,还需判断角色(
role === 'admin')或所有权(user.id === userId)。管理员可以删除任意用户,普通用户只能删除自己——这是"对象级授权"(object-level authorization)的典型写法; - 统一错误语义:通过自定义的
unauthorized错误类型抛出统一的 401 语义,便于前端和日志系统一致地识别处理。
纵深防御:认证、授权、输入校验三步走
如果 action 接收外部输入,规则进一步要求在认证之前先完成输入校验,整体遵循 "Validate → Authenticate → Authorize → Mutate" 的固定顺序。使用zod对未知类型输入做运行时校验是推荐做法:
'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) { // Validate input first const validated = updateProfileSchema.parse(data) // Then authenticate const session = await verifySession() if (!session) { throw new Error('Unauthorized') } // Then authorize if (session.user.id !== validated.userId) { throw new Error('Can only update own profile') } // Finally perform the mutation await db.user.update({ where: { id: validated.userId }, data: { name: validated.name, email: validated.email } }) return { success: true } }注意data的类型被声明为unknown——这正是 zod 的核心使用场景:Server Action 的入参来自客户端网络请求,属于不可信数据,必须先经过updateProfileSchema.parse()强制出结构正确的对象,才能安全进入后续逻辑。schema 中的约束本身就是活文档:
userId: z.string().uuid()—— 强制 UUID 格式,拒绝任意字符串注入;name: z.string().min(1).max(100)—— 长度上下限,防脏数据入库;email: z.string().email()—— 格式校验。
校验完成后才做认证(verifySession)与授权(session.user.id !== validated.userId拒绝越权修改他人资料),最后才触碰数据库。这四条顺序一旦颠倒,就可能出现"先查询后拦截"或"先写入后校验"的漏洞窗口。
为什么不能只依赖 middleware / 布局守卫
从源码结构看,Next.js 的 middleware 运行在边缘层、页面守卫与布局检查运行在渲染层,它们都无法覆盖"直接调用 action"这一路径。这带来两点现实结论:
- middleware 适合做前置的粗粒度过滤(如封禁 IP、全局登录跳转),但不应被视为安全边界:它拦截不了绕过路由系统直达 action 的调用,也容易因配置遗漏而出现"看似保护了所有路由、唯独漏掉某个 action"的局面;
- 布局与页面守卫只能保护 UI 可见性(如未登录用户看不到管理按钮),属于"体验层"而非"安全层"。攻击者不经过 UI,守卫就形同虚设。
因此规则给出的硬性要求是:在每一个会执行写操作(mutation)的 Server Action 内部,重复进行认证与授权检查。虽然这看起来是重复代码,但它是唯一能确保"无论调用来自哪条路径"都生效的兜底防线。
结合同技能的相邻规则:让认证查询更高效
server-auth-actions并非孤立规则。同属server-前缀的 server-cache-react.md 指出,认证检查(authentication checks)正是React.cache()每请求去重的最典型受益场景:页面、多个 Server Component 与多个 Server Action 在同一请求内各自调用verifySession()时,可以通过cache()让整条请求只执行一次会话查询:
import { cache } from 'react' export const getCurrentUser = cache(async () => { const session = await auth() if (!session?.user?.id) return null return await db.user.findUnique({ where: { id: session.user.id } }) })把认证逻辑包装进cache()后,同一请求内多次调用只会触发一次数据库查询,既保留了"每个 action 内部都要鉴权"的安全语义,又消除了重复查询的开销——安全与性能在这个模式中可以兼得。需要留意的是,React.cache()基于参数浅比较判断缓存命中,若必须传对象参数,应传入同一引用(参见同文件中的示例说明)。
仓库中的落地方式:技能打包与触发测试
在本仓库中,react-best-practices并非一份孤立文档,而是被集成进 gsd 的扩展技能体系:
- src/resources/extensions/gsd/bootstrap/system-context.ts 中注册了触发词:"React/Next.js performance — components, data fetching, bundle optimization, rendering patterns from Vercel Engineering",当 Agent 进入 React/Next.js 代码相关任务时自动匹配
react-best-practices技能; - src/resources/extensions/gsd/skill-catalog.ts 的"React & Web Frontend"目录将
vercel-react-best-practices归入官方技能源,并按matchLanguages: ["javascript/typescript"]匹配 TS/JS 项目; - src/resources/extensions/gsd/tests/bundled-skill-triggers.test.ts 将
react-best-practices列入内置技能清单,由测试断言其触发词被正确注册。
这意味着该规则的实际用途是指导 Agent 在编写、审查或重构 React/Next.js 代码时自动套用安全模式:当生成的 Server Action 缺少内部鉴权时,应当依据此规则补上认证与授权代码,而不是仅依赖 middleware 等外部机制。
实施自查清单
把本规则落地到自己的项目时,可以按以下清单逐项核对:
- 每个
'use server'导出函数都以会话校验开头,未认证即抛unauthorized,不执行任何后续逻辑; - 授权判断覆盖对象级权限:不仅检查"是否登录",还检查角色/资源所有权(如
session.user.id !== targetId时拒绝); - 所有外部输入先经 schema 校验(推荐 zod,入参类型声明为
unknown),且校验先于认证、认证先于授权、授权先于写操作; - 错误语义统一:认证/授权失败使用可识别的 401 类错误,便于前端统一处理(如跳转登录页);
- middleware / 布局守卫仅作辅助:可保留作为体验优化,但绝不在安全上依赖它们;
- 认证查询做去重:对会话/用户查询使用
React.cache()(或依赖 Next.js 对fetch的自动 memoization),避免同一请求内重复查询。
将server-auth-actions与仓库中的 server-serialization.md、server-cache-react.md 等规则组合使用,就能在 Server Actions 这一侧同时守住安全、性能与数据体积三条线。核心心法只有一句:Server Actions 没有"内部"与"外部"之分,每个 action 都是面向公众的入口,鉴权必须发生在入口本身。
- 人工智能
- AI Agent
- 代码智能体
- Agent 编排
- CLI
- AI 应用
【免费下载链接】gsd-2
A powerful meta-prompting, context engineering and spec-driven development system that enables agents to work for long periods of time autonomously without losing track of the big picture
相关推荐
QuickRecorder macOS 录屏工具教程:从安装到第一次录制
QuickRecorder macOS 录屏工具教程:从安装到第一次录制 QuickRecorder 是一款基于系统 ScreenCapture Kit 开发的
桌面应用音视频屏幕录制ROG 屏幕发白不用愁?G-Helper 色彩修复实战
ROG 屏幕发白不用愁?G Helper 色彩修复实战 如果你发现 ROG 或华硕笔记本屏幕最近发白、颜色发灰,切换色彩模式又没反应,先别急着怀疑硬件。多数情况
开发工具AI 应用代码智能体像 API 路由一样为 Server Actions 鉴权:Next.js 服务端操作安全实践
像 API 路由一样为 Server Actions 鉴权:Next.js 服务端操作安全实践 Server Action( "use server" 函数)与
音视频桌面应用后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考