Next.js 14中间件中执行SQL查询验证JWT以实现安全路由保护的问题
Next.js 14中间件中执行SQL查询验证JWT以实现安全路由保护的问题
兄弟我太懂你这烦恼了!刚上手Next.js 14做认证路由的时候,我也卡在中间件不能直接连数据库这事儿上——你现在的代码确实完全没安全性,随便整个token cookie就能绕过去,得赶紧改。
首先得说清楚为啥中间件里不能直接用Prisma:Prisma客户端的初始化逻辑和边缘环境(Next.js中间件跑在Edge Runtime里)不太兼容,尤其是本地数据库,根本没法直接建立连接。那怎么解决?给你两个靠谱的方案:
方案一:把验证逻辑移到API路由,中间件调用API做校验
这是最稳妥的办法,本地开发和生产环境都能用。思路就是:把查数据库、验证JWT的逻辑放到一个内部API路由里,中间件只负责把token传给这个API,根据返回结果决定放不放行。
第一步:写验证token的API路由
在app/api/validate-token/route.ts里写这个逻辑:
import { NextResponse } from 'next/server'; import prisma from '@/lib/prisma'; // 你的Prisma实例路径 import jwt from 'jsonwebtoken'; export async function POST(request: Request) { const { token } = await request.json(); if (!token) { return NextResponse.json({ valid: false }, { status: 400 }); } // 先查数据库里有没有这个token const user = await prisma.user.findFirst({ where: { jwtToken: token }, // 假设你的user表有存token的字段 }); if (!user) { return NextResponse.json({ valid: false }, { status: 401 }); } // 再验证JWT本身的签名和过期时间,这步非常重要! try { jwt.verify(token, process.env.JWT_SECRET!); return NextResponse.json({ valid: true }, { status: 200 }); } catch (err) { // 签名无效或者token过期 return NextResponse.json({ valid: false }, { status: 401 }); } }
第二步:修改中间件代码
现在中间件里不再直接碰数据库,而是调用上面的API:
import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export async function middleware(request: NextRequest) { const tokenCookie = request.cookies.get('token'); // 先检查有没有token cookie if (!tokenCookie) { return NextResponse.redirect(new URL('/login', request.url)); } try { // 调用内部API验证token const validateResponse = await fetch( new URL('/api/validate-token', request.url), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token: tokenCookie.value }), cache: 'no-store', // 禁止缓存,每次都查最新状态 } ); const { valid } = await validateResponse.json(); if (!valid) { // token无效的话,清掉cookie再重定向到登录页 const redirectResponse = NextResponse.redirect( new URL('/login', request.url) ); redirectResponse.cookies.delete('token'); return redirectResponse; } // 验证通过,放行 return NextResponse.next(); } catch (err) { // 网络错误或者其他异常,直接打回登录页 return NextResponse.redirect(new URL('/login', request.url)); } } export const config = { matcher: '/home', };
方案二:用Prisma的Edge版本(仅适合生产托管数据库)
如果你用的是托管数据库(比如PlanetScale、Supabase),可以试试Prisma的Edge客户端版本,这样就能在中间件里直接用Prisma了。不过本地开发的话,本地数据库还是没法和Edge Runtime兼容,所以还是方案一更实用。
额外注意事项
- 一定要验证JWT本身:不能只查数据库里有没有这个token,还要用
jsonwebtoken库验证签名和过期时间,防止token过期或者被篡改。 - 环境变量存密钥:JWT的密钥一定要存在
.env里,绝对不能硬编码到代码里。 - 可以加缓存优化:如果同一个token短时间内多次请求,可以在API路由里加个内存缓存(比如用
lru-cache),避免重复查数据库。 - 中间件fetch用内部URL:别用外部域名,就用
/api/xxx这种内部路径,避免跨域和性能问题。
备注:内容来源于stack exchange,提问作者jeyremd
相关产品推荐
相关产品推荐

