Next.js middleware校验JWT抛出无法解析crypto模块错误
问题根因
Next.js 12.2版本稳定后的Middleware默认运行在Edge Runtime环境,该运行时基于Web标准API构建,不兼容Node.js原生crypto模块。jsonwebtoken库的底层依赖jwa、jws均强绑定Node.js原生crypto实现JWT签名、校验逻辑,因此只要在Middleware中调用jwt.verify方法,就会触发Module not found: Can't resolve 'crypto'报错,和路由重定向逻辑本身无关——注释掉verify调用后逻辑正常运行,也能印证这一点。
可行解决方案
按推荐优先级从高到低排列:
- 替换为Edge Runtime原生兼容的JWT库
直接卸载jsonwebtoken,改用基于Web Crypto API实现的jose库,这是Next.js官方推荐在Edge场景使用的JWT工具,无Node原生模块依赖,可直接在Middleware中正常运行。- 安装依赖:
npm install jose # 用yarn/pnpm的话,对应执行yarn add jose / pnpm add jose即可- 改造Middleware鉴权逻辑:
// middleware.ts import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' import { jwtVerify } from 'jose' // jose要求密钥转换为Uint8Array格式,直接从环境变量读取即可 const JWT_SECRET = new TextEncoder().encode(process.env.JWT_SECRET) export async function middleware(request: NextRequest) { // 仅拦截/dashboard路径下的所有路由 if (request.nextUrl.pathname.startsWith('/dashboard')) { const token = request.cookies.get('token')?.value // 无token直接跳登录 if (!token) { return NextResponse.redirect(new URL('/login', request.url)) } try { // 校验JWT签名、有效期是否合法 await jwtVerify(token, JWT_SECRET) return NextResponse.next() } catch { // token无效、过期、被篡改均跳登录 return NextResponse.redirect(new URL('/login', request.url)) } } return NextResponse.next() } // 配置匹配规则,避免Middleware拦截不必要的静态资源等请求 export const config = { matcher: '/dashboard/:path*' }- 同步改造登录接口的JWT签发逻辑,和原有业务逻辑完全对齐即可:
// /pages/api/auth/login.ts import { SignJWT } from 'jose' import type { NextApiRequest, NextApiResponse } from 'next' const JWT_SECRET = new TextEncoder().encode(process.env.JWT_SECRET) export default async function handler(req: NextApiRequest, res: NextApiResponse) { // 保留原有账号密码校验逻辑,校验通过后签发JWT // const user = await validateAccount(req.body.username, req.body.password) // if (!user) return res.status(401).json({ message: '账号或密码错误' }) const token = await new SignJWT({ userId: user.id, username: user.username // 其余需要写入payload的字段和之前保持一致 }) .setProtectedHeader({ alg: 'HS256' }) .setExpirationTime('7d') // 替换为实际需要的有效期 .sign(JWT_SECRET) // 原有写cookie、返回响应的逻辑不需要改动 res.setHeader('Set-Cookie', `token=${token}; Path=/; HttpOnly; Max-Age=604800; SameSite=Lax`) return res.status(200).json({ success: true }) } - 拆分鉴权逻辑,不在Middleware层做签名校验
如果不想替换现有jsonwebtoken相关代码,可以保留Middleware中判断token是否存在的逻辑,把JWT有效性校验下沉到Node.js运行时层处理:比如在getServerSideProps、服务端组件、API路由中用jsonwebtoken的verify方法做校验,无效则返回重定向响应。
该方案的缺点是无效token的请求会穿透Edge层到达Node服务,相比Edge层直接拦截会有额外的性能损耗,但是不需要改动现有JWT相关的业务代码。 - 强制Middleware运行在Node.js Runtime(不推荐)
Next.js支持手动配置Middleware的运行时,在middleware.ts文件顶部添加如下配置即可让代码跑在Node环境,原生支持crypto模块:
该方案会丢失Edge Runtime冷启动快、可边缘部署的优势,且部分边缘部署平台对Node.js Runtime的Middleware存在兼容限制,生产环境不优先推荐。export const runtime = 'nodejs'
避坑说明
不要尝试通过polyfill的方式给Edge Runtime补全crypto模块:JWT校验涉及密码学操作,第三方polyfill普遍存在实现不完整、体积过大、安全漏洞等问题,无法满足生产环境要求。
内容的提问来源于stack exchange,提问作者Philipos D.
相关产品推荐
相关产品推荐

