先记住这个答案
在 Route Handler 中,先检查 Authorization 请求头,提取 Bearer token,用密钥校验签名和过期时间,验证通过后把用户信息(如 userId)写入自定义请求头传入后续代码。若 token 缺失或无效,返回 401。
- JWT 验证逻辑应封装为可复用函数
- 身份信息通过自定义请求头传递
- 失败统一返回 401 且不暴露细节
Route Handler 中的请求拦截与验证流程
在 Next.js App Router 中,app/api/*/route.ts 导出的函数即 Route Handler。它接收 NextRequest 对象,可访问原始请求头。要实现认证中间件,可在函数入口先检查是否存在 Authorization 头,其格式应为 Bearer <token>,否则直接返回 401。这一步是拦截未认证请求的核心。
取得 token 后,使用 jose 库或 jsonwebtoken 验证 JWT 签名与有效期。验证通过后,从 payload 提取用户标识(如 sub)。不要将用户对象直接放入函数闭包,而是创建一个新请求,通过 request.headers 的副本加入自定义头,如 x-user-id,再传给后续业务处理。这样下游代码可从入口函数的 request.headers 读取该值,实现上下文传递。
由于 Request 的 headers 是只读的,需要先克隆原请求头得到 Headers 实例,调用 set 方法,再传入 new Request(request, { headers: newHeaders })。这只影响新请求,不影响外部。此外,应捕获验证抛出的异常,如 JWTExpired 或 JWTMalformed,统一返回 401 或 403。
构建受保护的 `/api/user/profile` 接口
假设要提供当前登录用户基本信息,前端发送 GET /api/user/profile,携带 Authorization: Bearer <jwt>。在 app/api/user/profile/route.ts 中,我们调用一个 requireAuth 辅助函数。该函数读取头部,验证 token,若有效则返回用户 ID,否则抛出特定异常。随后 handler 根据用户 ID 查询并返回资料;伪造 token 会被拒绝。
关键决策是身份信息如何传递:我们不把用户对象一直携带,而是仅在请求头中放入最小标识 x-user-id。这样做能避免在日志中泄露敏感数据,同时便于日志分析与依赖追踪。由于该头是内部约定,需设置 Access-Control-Allow-Headers 包含自定义头,以免浏览器跨域请求被拦截。
JWT 认证的局限与正确做法
此模式仅适合无状态 JWT,token 一旦签发,必须等待过期;若需即时撤销,必须配合黑名单或改用 session。另外,每次请求都要重新校验签名,CPU 开销有限,但依赖时钟同步;若多个实例,需统一密钥来源,避免硬编码。
不要将认证逻辑直接写成内嵌,而应抽取为高阶函数包装器,避免重复代码。由于 Route Handlers 在 Edge Runtime 或 Node.js 上执行,需确保所用加密库兼容。错误响应应通用,避免透露 token 无效原因,防止侧信道攻击。
容易答错的地方
- 认为 JWT 验证在中间件层做更好
- Next.js Middleware 运行在 Edge,只支持有限功能,而 Route Handler 可在 Node.js 环境使用完整 JWT 库,且能感知路由级配置,在 Route Handler 内封装更灵活,且不影响页面渲染。
- 把用户身份写在局部变量而非请求头
- 官方未支持在
NextRequest上随意添加属性,自定义属性可能被忽略或丢失;通过headers传递是标准做法,还能被日志系统记录,便于追踪调试。
面试官还会怎么问?
如何处理 token 过期与刷新?
过期时应返回 401,前端收到后跳转登录;刷新可通过独立 refresh token 端点,由客户端定时调用获取新 token,不在此认证中间件职责范围内。
如何对特定 API 路径排除 JWT 验证?
可在 requireAuth 函数开头检查路径或方法,维护一个白名单数组,命中则直接跳过验证,避免将公共接口误伤。
能否让 Route Handler 读取从中间件设置的请求头?
可以,中间件通过 NextRequest rewrite 可改写请求头,但 Route Handler 最终接收的是改写后的请求;内部认证逻辑应在 Handler 入口处理,而不是依赖中间件传递。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。