Next.js Middleware 与鉴权:生活应用的安全层不能只靠客户端判断
Next.js Middleware 与鉴权:生活应用的安全层不能只靠客户端判断
一、生活数据的鉴权比业务应用更敏感但资源更少
生活应用的数据鉴权有独特矛盾。数据本身比业务应用更敏感——日记、情绪记录、消费详情都是高度私密信息。但生活应用的开发资源和运维投入远少于企业级产品。很多独立开发的生活应用甚至没有专门的运维人员。
鉴权如果只靠客户端判断,风险很高。客户端的 localStorage 或 session 可以被篡改,请求参数可以被修改。一个生活应用如果只在前端检查用户身份,任何人都可以伪造请求获取其他用户的日记内容。这不是理论风险,而是生活应用常见的实际漏洞。
Next.js Middleware 提供了服务端鉴权的入口点。它在请求到达页面之前执行,可以验证身份、检查权限、拦截未授权访问。生活应用应该把鉴权放在 Middleware 层,而不是只依赖客户端的 UI 判断。
二、Middleware 鉴权的层级设计
鉴权应该分层执行。Middleware 做身份验证和粗粒度权限检查,页面层做细粒度数据权限控制。
flowchart TD A[用户请求] --> B[Middleware:身份验证] B --> C{有效身份?} C -->|否| D[重定向到登录页] C -->|是| E[Middleware:路由权限检查] E --> F{允许访问此路由?} F -->|否| G[返回403] F -->|是| H[页面层:数据权限控制] H --> I[只返回该用户自己的数据] I --> J[页面渲染]两层鉴权的分工明确。Middleware 确保"这个人是谁"和"他能不能看这个页面",页面层确保"他只能看到自己的数据"。粗粒度用 Middleware 快速拦截,细粒度在页面层精确过滤。
三、Next.js Middleware 鉴权实现
// middleware.ts // Next.js Middleware:生活应用的身份验证和路由权限检查 import { NextRequest, NextResponse } from "next/server"; // 需要鉴权的路由列表 const protectedRoutes = ["/diary", "/emotion", "/expense", "/settings"]; // 公开路由:不需要鉴权 const publicRoutes = ["/login", "/register", "/about"]; export function middleware(request: NextRequest) { const { pathname } = request.nextUrl; // 公开路由直接放行 if (publicRoutes.some((r) => pathname.startsWith(r))) { return NextResponse.next(); } // 检查鉴权token const token = request.cookies.get("auth_token")?.value; if (!token) { // 未登录用户重定向到登录页,保留原始URL便于登录后跳回 const loginUrl = new URL("/login", request.url); loginUrl.searchParams.set("redirect", pathname); return NextResponse.redirect(loginUrl); } // 验证token有效性(简单签名检查,不调用外部服务) if (!verifyTokenSignature(token)) { // token无效时清除cookie并重定向到登录 const response = NextResponse.redirect(new URL("/login", request.url)); response.cookies.delete("auth_token"); return response; } return NextResponse.next(); } // 简单token签名验证:防止客户端伪造 function verifyTokenSignature(token: string): boolean { try { // token格式:base64(userId).base64(signature) const parts = token.split("."); if (parts.length !== 2) return false; const [payload, signature] = parts; // 使用环境变量中的密钥验证签名 const expectedSig = signPayload(payload, process.env.AUTH_SECRET ?? ""); return signature === expectedSig; } catch { return false; } } export const config = { matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"], };Middleware 不应该做外部服务调用(比如调用数据库验证用户详情)。Middleware 的执行频率高,每次请求都会经过,外部调用会增加延迟和失败风险。身份验证用 token 签名检查,数据权限在页面层做数据库查询。
四、Middleware 的性能和部署边界
Middleware 在 Vercel 上的执行有冷启动问题。首次请求时 Middleware 需要初始化,可能增加几百毫秒延迟。生活应用的流量通常不大,冷启动影响有限。但如果流量波动大(比如推送后突然涌入用户),冷启动会集中发生。
Middleware 的另一个限制是不能访问 Node.js 的完整 API。它运行在 Edge Runtime 上,不支持 fs、crypto 等模块。上面的签名验证用了简单字符串比较而非 crypto 模块,就是为了适配 Edge Runtime。生产环境应该用 jose 等 Edge 兼容的 JWT 库做更安全的验证。
生活应用还需要考虑多设备登录的场景。用户可能在手机和电脑上同时使用。token 签发时应该包含设备信息,不同设备的 token 管理应该独立。单设备踢出不应该影响其他设备的登录状态。
五、总结
生活应用鉴权应在 Next.js Middleware 层做身份验证和路由权限检查,页面层做数据权限过滤。Middleware 不做外部服务调用,只用 token 签名验证确保快速响应。公开路由直接放行,受保护路由未登录时重定向到登录页。Edge Runtime 限制下用兼容库替代 Node.js API。多设备登录需要独立 token 管理。
