- 人工智能
- 大模型
- AI 应用
- 前端
- 后端
【免费下载链接】BibiGPT-v1
BibiGPT v1 · one-Click AI Summary for Audio/Video & Chat with Learning Content: Bilibili | YouTube | Tweet丨TikTok丨Dropbox丨Google Drive丨Local files | Websites丨Podcasts | Meetings | Lectures, etc. 音视频内容 AI 一键总结 & 对话:哔哩哔哩丨YouTube丨推特丨小红书丨抖音丨快手丨百度网盘丨阿里云盘丨网页丨播客丨会议丨本地文件等 (原 BiliGPT 省流神器 & AI课代表)
导读:Server Actions(带
"use server"指令的函数)与 API 路由一样会被暴露为公开端点,任何人都可能绕过页面直接调用它们。本文以 Vercel React/Next.js 最佳实践规则 server-auth-actions.md 为主线,讲解为什么必须在每个 Server Action内部完成认证与授权,并给出完整的输入验证 + 认证 + 授权代码范式;同时结合 BibiGPT-v1 仓库中 proxy.ts 与 pages/api/sumup.ts 的真实实现,说明在生产级 Next.js 应用中"服务端入口统一鉴权"是如何落地的。读完你将掌握一套可复制的 Server Actions 安全加固模板,并理解它与 middleware 鉴权的边界关系。
一、为什么 Server Actions 必须被当作公开端点对待
在 Next.js 的 App Router 中,带有"use server"指令的函数会被框架注册为服务端可调用端点,浏览器端可以通过action属性或startTransition直接触发,其传输机制与传统的 POST 请求并无本质区别。这意味着:
- Server Actions 具备独立的 HTTP 可寻址性,不依赖任何页面上下文;
- 攻击者完全可以绕过 UI、绕过布局组件(layout guards)、绕过页面级检查,直接向该端点发起请求;
- 一旦某个 Server 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."(将 Server Actions 视为面向公众的 API 端点,并在执行变更前验证用户是否被允许执行该操作。)
这条规则在 Vercel React Best Practices 体系中被标记为Impact: CRITICAL(防止对服务端变更操作的未授权访问),归属于 Server-Side Performance(HIGH)类别下的最高优先级安全条目,其原始定义位于 server-auth-actions.md,并在编译产物 AGENTS.md(3.1 节)中同步维护。核心结论可以归纳为一句话:
认证与授权必须发生在每一个 Server Action 的函数体内,绝不能依赖 middleware、布局守卫或页面级检查。
二、错误示范:完全没有认证检查的 Server Action
以下反例是最常见的漏洞模式——函数对外暴露了"删除用户"的能力,却没有做任何身份校验:
'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 } }问题分析:
- 任何人只要拿到该 Action 的调用途径,就可以传入任意
userId删除任意用户; - 即使页面被隐藏了删除按钮、即使页面外层的 layout 做了登录拦截,都不影响该 Action 被直接调用;
db.user.delete这种破坏性变更一旦执行不可回滚,后果是数据层的永久性损失。
这提醒我们一个容易被忽略的事实:UI 上的"入口隐藏"不等于"服务端的权限控制"。前端任何形式的拦截都只是体验层面的设计,真正的安全边界必须建立在服务端函数内部。
三、正确范式:在 Action 内部先认证、再授权
正确做法是把鉴权逻辑放进 Server Action 的函数体最前部,形成"认证 → 授权 → 执行变更"的三段式结构:
'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 } }这段代码体现了两个层次的安全校验:
- 认证(Authentication):
verifySession()从请求上下文中解析当前会话。会话不存在 → 直接拒绝,防止未登录用户调用; - 授权(Authorization):即便已登录,还要判断"该用户是否有权执行此操作"。示例中的判定逻辑是:管理员可以删除任意用户,普通用户只能删除自己。这一层防止了已登录用户越权操作他人数据。
两者缺一不可:认证解决"你是谁"的问题,授权解决"你能不能做"的问题。只做认证不做授权,仍然存在水平越权(IDOR)风险。
四、完整生产范式:输入验证 + 认证 + 授权 + 变更
真实业务中的 Server Action 还需要在鉴权之前先对入参做结构校验,避免脏数据进入后续逻辑。规则给出的完整示例使用zod定义 Schema,形成"先验证输入、再认证、再授权、最后执行变更"的固定顺序:
'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 } }各步骤的必要性拆解:
| 步骤 | 代码位置 | 作用 | 典型失败后果 |
|---|---|---|---|
| 输入验证 | zodSchema +parse(data) | 约束字段类型、长度、格式(如uuid、email) | SQL 注入、超长字段写入、类型攻击 |
| 认证 | verifySession() | 确认调用者已登录 | 未登录用户直接调用 |
| 授权 | session.user.id !== validated.userId | 确认调用者只能操作自己的数据 | 越权修改他人资料 |
| 变更执行 | db.user.update(...) | 在全部校验通过后才落库 | 未经验证的写操作 |
注意两个细节:
- 参数类型写成
unknown:Server Action 的入参不应直接声明为业务对象类型并盲信,先用 zod 解析收窄,才能保证后续代码操作的是受信任的数据; - 授权校验依赖"经过验证的输入":示例中先
parse拿到可信的validated.userId,再与session.user.id对比,避免了直接信任客户端传入的userId导致的越权。
这条"验证 → 认证 → 授权 → 变更"的顺序同样适用于删除、更新、写入等一切**服务端变更(mutation)**场景,是可以在整个应用内统一复用的模板。
五、BibiGPT-v1 仓库中的服务端鉴权实况
当前仓库以 Next.js(next^16.1.6,见 package.json)构建,采用 Pages Router + 中间件proxy.ts的架构,尚未使用"use server"指令(在 pages 与 lib 中搜索"use server"无命中),其"服务端变更入口"是 API 路由与中间件的组合。但它完整践行了"服务端入口必须自行鉴权"的同一安全原则,可作为对照参考。
5.1 中间件层:按用户身份分流限流
BibiGPT-v1 在 proxy.ts 中注册了针对/api/sumup与/api/chat的中间件(见proxyConfig.matcher,L155-L157),只处理POST且Content-Type为 JSON 的请求。在中间件内部,认证与授权是按调用者身份分路径完成的:
- 携带用户 API Key 的调用:先通过 checkOpenaiApiKeys 判断是否为合法的 OpenAI Key;不是则视为许可密钥(license key),转由 validateLicenseKey 调用 Lemonsqueezy License API 校验;
- 未携带 Key 的调用(生产环境):先按 IP 走免费额度限流(
ratelimitForIps,配置于 lib/upstash.ts,fixedWindow(FREE_LIMIT_COUNT, '1 d'));IP 限流耗尽后,再通过createMiddlewareSupabaseClient({ req, res })读取 Supabase 会话:
// proxy.ts L112-L129(节选) const supabase = createMiddlewareSupabaseClient({ req, res }) const { data: { session }, } = await supabase.auth.getSession() // Check auth condition const userEmail = session?.user.email if (userEmail) { // Authentication successful, forward request to protected route. const { success, remaining } = await ratelimitForFreeAccounts.limit(userEmail) ... return res } // todo: throw error to trigger a modal, rather than redirect a page return redirectAuth(req)从这段实现可以清晰看到:登录用户(有 session)与匿名用户(只有 IP)走的是完全不同的两条限流与授权通道,会话校验失败会统一返回 401(redirectAuth,L28-L37)。这正是"服务端入口自行鉴权"的工程化体现——中间件并不假设"能到达这里的都是好人"。
5.2 API 路由层:方法校验 + 参数校验 + 异常兜底
在 pages/api/sumup.ts 中,真正的处理函数handler同样在入口处完成了一系列自校验:
// pages/api/sumup.ts L12-L22(节选) export default async function handler(req: NextApiRequest, res: NextApiResponse) { if (req.method !== 'POST') { res.setHeader('Allow', 'POST') return res.status(405).json({ errorMessage: 'Method Not Allowed' }) } const summarizeParams = req.body as Partial<SummarizeParams> if (!summarizeParams?.videoConfig || !summarizeParams?.userConfig) { return res.status(400).json({ errorMessage: 'Missing videoConfig or userConfig' }) } ... }- 方法校验:非
POST直接返回 405,拒绝一切非预期动词; - 参数完整性校验:缺少
videoConfig或userConfig直接返回 400,不进入后续逻辑; - 统一异常兜底:
SummarizeRequestError映射为对应的状态码返回,其余未知异常统一降级为 500(L42-L50)。
随后才进入业务链路:buildSummarizeOpenAIPayload构造载荷 → selectApiKeyAndActivatedLicenseKey 选择 API Key 或激活许可密钥 →fetchOpenAIResult拉取 AI 结果。这套"入口校验先行、业务随后"的结构,与规则文档中 Server Action 的"验证 → 认证 → 授权 → 变更"顺序在精神上完全一致。
5.3 从仓库实现反推的迁移启示
如果未来把 BibiGPT-v1 的这些写接口迁移为 Server Action,那么依据规则文档,下面这些中间件与 API 路由里的校验必须在每个 Action 内部重新执行一遍:
- 会话解析(对应
supabase.auth.getSession(),见 proxy.ts L116); - 身份分流与限流(对应 lib/upstash.ts 中的
ratelimitForIps/ratelimitForFreeAccounts/ratelimitForApiKeyIps); - 许可密钥校验(对应 lib/lemon.ts 的
validateLicenseKey); - 参数完整性校验(对应 pages/api/sumup.ts L19-L21 的 400 检查)。
因为 Server Action 可被直接调用,middleware 在它面前只是"第一道防线"而非"唯一防线"——这正是本规则强调的"不要依赖 middleware"的现实意义:迁移后若只保留中间件层鉴权而忘记在 Action 内重复校验,就等于给所有变更接口重新开了后门。
六、落地清单:Server Actions 安全加固自检
将本文要点整理为可直接对照的检查清单,供你在编写或评审 Server Action 时逐项核验:
- 每个写操作 Action 的函数体内,是否在首部调用了会话验证函数?
- 除认证外,是否针对"操作对象归属"做了授权判定(如
session.user.id !== targetUserId)? - 入参是否声明为
unknown并先经过 zod 等 Schema 校验,再进入业务逻辑? - 是否假设"到达 Action 的请求一定经过了页面或 middleware 的拦截"?——该假设必须消除,所有校验在 Action 内部独立成立;
- 授权失败与未认证是否抛出明确的错误(如
unauthorized/'Unauthorized'),而非静默放行或返回成功? - 变更操作(delete / update / write)是否全部落在校验之后执行?
七、总结
Server Actions 因为"形似普通函数"而容易被开发者误当成受保护的后端方法,但它在网络层面的暴露程度与 API 路由并无二致。本文以 server-auth-actions.md 为骨架,给出了三层完整的防御范式:
- 第一层:函数内部认证(
verifySession),确认调用者身份; - 第二层:函数内部授权(角色 / 资源归属判断),阻止越权;
- 第三层:函数入口输入验证(zod),拒绝脏数据进入变更链路。
同时,通过 BibiGPT-v1 的 proxy.ts 与 pages/api/sumup.ts 展示了同类安全边界在生产代码中的真实形态——无论是中间件、API 路由还是 Server Action,"服务端入口必须自己证明调用者的合法性"是永恒不变的原则。把这套模板固化到你的 Server Action 中,即可为每一次服务端变更操作筑起完整的认证、授权与输入校验防线。
- 人工智能
- 大模型
- AI 应用
- 前端
- 后端
【免费下载链接】BibiGPT-v1
BibiGPT v1 · one-Click AI Summary for Audio/Video & Chat with Learning Content: Bilibili | YouTube | Tweet丨TikTok丨Dropbox丨Google Drive丨Local files | Websites丨Podcasts | Meetings | Lectures, etc. 音视频内容 AI 一键总结 & 对话:哔哩哔哩丨YouTube丨推特丨小红书丨抖音丨快手丨百度网盘丨阿里云盘丨网页丨播客丨会议丨本地文件等 (原 BiliGPT 省流神器 & AI课代表)
相关推荐
Stagewise 技能库安全实践:像 API 路由一样认证 Next.js Server Actions
Stagewise 技能库安全实践:像 API 路由一样认证 Next.js Server Actions Server Actions( 'use serve
人工智能AI Agent代码智能体开发工具Next.js Server Actions 安全实战:像 API 路由一样在 Action 内部完成认证与授权(next-shadcn-dashboard-starter 实践)
Next.js Server Actions 安全实战:像 API 路由一样在 Action 内部完成认证与授权(next shadcn dashboard s
前端UI组件像 API 路由一样为 Server Actions 鉴权:Next.js 服务端操作安全实践
像 API 路由一样为 Server Actions 鉴权:Next.js 服务端操作安全实践 Server Action( "use server" 函数)与
音视频桌面应用后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考