Express.js中Passport.js JWT中间件查询用户状态是否合理?
问题背景
我开发了一个Node.js应用,采用Passport.js的JWT策略。向客户端发放JWT后,用户可能会注销账号,但由于已提前获取JWT,仍可在JWT过期前继续请求其他接口。想知道在Passport.js中间件层通过额外数据库查询检查用户状态是否合理,或是存在其他解决方案?
原代码片段:
const passportStrategy = (passport: { use: (arg0: passportJWT.Strategy) => void; }) => { passport.use( new JWTStrategy( { jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(), secretOrKey: process.env.JWT_AUTH_KEY!, passReqToCallback: true, }, async ( req: Request, payload: any, done: passportJWT.VerifiedCallback ) => { const src = DataSourceFactory.source; //db query in here try { }); return done(null, user); } catch (findUserError) { return done(findUserError); } } ) ); };
解决方案分析
一、中间件层加数据库查询的合理性
这种方案完全合理,是处理即时注销需求的直接手段:
- 优势:逻辑简单直接,能确保每次请求都验证用户当前状态,不会出现已注销用户仍能操作的情况。
- 劣势:每次请求都增加数据库查询,会提升系统开销,高并发场景下可能影响性能。如果你的应用并发量不高,或者对安全性的优先级高于性能,这就是最优选择。
补全后的代码示例:
const passportStrategy = (passport: { use: (arg0: passportJWT.Strategy) => void; }) => { passport.use( new JWTStrategy( { jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(), secretOrKey: process.env.JWT_AUTH_KEY!, passReqToCallback: true, }, async ( req: Request, payload: any, done: passportJWT.VerifiedCallback ) => { const src = DataSourceFactory.source; try { // 仅查询用户ID和状态字段,减少数据库开销 const user = await src.getRepository(User).findOne({ where: { id: payload.userId }, select: ['id', 'isActive'] }); if (!user || !user.isActive) { // 用户不存在或已注销,返回认证失败 return done(null, false, { message: '用户已注销或不存在' }); } return done(null, user); } catch (findUserError) { return done(findUserError); } } ) ); };
二、其他替代方案
1. 缩短JWT过期时间 + 刷新令牌机制
- 核心思路:把JWT的过期时间设得很短(比如15分钟),同时发放一个有效期更长的刷新令牌。用户需要用刷新令牌定期获取新的JWT。
- 注销时,直接将刷新令牌加入黑名单,这样用户无法获取新的JWT,旧JWT过期后就无法继续操作。
- 优势:大幅减少数据库查询次数,只有在刷新令牌时才需要验证状态或黑名单,性能更优。
- 注意:刷新令牌要存在数据库或Redis中,注销时标记为无效。
2. 维护JWT黑名单
- 核心思路:用户注销时,将当前有效的JWT加入黑名单(推荐存Redis,过期时间和JWT保持一致)。
- 在Passport中间件中,先验证JWT签名,再检查该JWT是否在黑名单中。
- 优势:避免每次查询用户状态,Redis的查询性能远高于数据库,且能自动清理过期的黑名单数据。
3. 敏感接口单独校验用户状态
- 核心思路:JWT设中等过期时间(比如1小时),只在关键接口(如支付、修改个人信息)中查询用户状态,普通接口依赖JWT过期时间自动失效。
- 优势:平衡安全性和性能,既保证高敏感操作的安全性,又减少普通请求的开销。
总结
- 如果对即时注销要求极高且并发量不大,直接在Passport中间件加数据库查询是最稳妥的选择;
- 若追求性能,优先考虑短JWT+刷新令牌+黑名单的组合;
- 敏感接口单独做状态验证,普通接口依赖JWT过期,适合大多数中型应用场景。
内容的提问来源于stack exchange,提问作者Nurettin Şenssabc
相关产品推荐
相关产品推荐

