Express.js中passport-jwt鉴权后Sequelize模型属性访问返回undefined
问题场景
我正在使用passport-jwt来验证express.js中指定路由的访问权限,认证完成后会将Sequelize模型返回给最终控制器,相关代码如下:
认证策略代码
const passportStrategy = passport => { const options = { jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), secretOrKey: config.auth.ACCESS_TOKEN_SECRET }; passport.use( new Strategy(options, async (payload, done) => { try { const user = await User.findOne({ where: { email: payload.email }}); if (user) { return done(null, { user }); } return done(null, false); } catch (error) { return done(error, false) } }) ); };
配置鉴权的路由代码
router.get('/:user_id/psy', passport.authenticate('jwt', { session: false }), patientsController.getPatientPsy);
控制器代码
const getPatientPsy = async (req, res) => { const authenticatedUser = req.user; if (authenticatedUser.userType !== "patient") { res.status(500).send("Big time error"); } }
遇到的异常现象
在控制器中执行console.log(authenticatedUser)时,可以成功打印出包含dataValues结构的Sequelize模型内容,但无论尝试访问哪个属性(不管是userType还是其他字段),拿到的结果始终是undefined。已经尝试把控制器改成同步函数,问题依然存在。
问题根因分析
这个问题是两个常见的代码编写误区叠加导致的:
1. passport策略返回值多包了一层对象
passport框架的规则是:done(error, 传递的用户对象)方法中,第二个参数会被**直接整体挂载到req.user**上。
你在策略里写的是:
// 这里传入的不是user实例本身,而是一个普通JS对象 return done(null, { user });
这等价于你告诉passport:把这个结构为{ user: Sequelize实例 }的普通对象直接挂到req.user上,此时req.user的实际结构是:
req.user = { user: User { // 这才是你查到的Sequelize实例,带dataValues结构 dataValues: { userType: 'patient', ... }, ... 其他Sequelize内部属性 } }
你在控制器里打印req.user时,看到的带dataValues的Sequelize结构,其实是这个普通对象嵌套的内部属性,并不是req.user本身。因为外层是没有任何Sequelize内置getter代理的普通JS对象,所以直接访问req.user.userType自然会返回undefined——这个外层对象根本没有userType属性,真正的用户数据嵌套在req.user.user这个属性下。
调试时很容易犯的错就是被控制台展开的嵌套结构误导,只看到显眼的Sequelize模型内容,没注意最外层的包裹键。
2. 直接传递Sequelize实例存在的兼容问题
就算去掉外层的包裹,直接把Sequelize实例传给done方法,也可能出现属性访问异常:Sequelize模型实例的属性值并不是直接挂在实例表层的,而是存在内部的dataValues里,通过内置的getter代理做访问。当passport在把对象挂载到req.user的过程中做浅拷贝、对象传递时,很可能丢失原型上的代理方法,导致虽然能在打印时看到内部的dataValues结构,却无法通过常规点语法取到属性值。
解决办法
按优先级调整两处代码即可:
- 去掉passport策略里多余的对象包裹,不要额外套一层
{user: xxx}的结构 - 把Sequelize实例转成纯普通JS对象再传给done方法,从根源上避免代理、序列化带来的属性访问问题
修改后的认证策略代码:
passport.use( new Strategy(options, async (payload, done) => { try { const user = await User.findOne({ where: { email: payload.email }}); if (user) { // 把Sequelize实例转为纯平铺的普通JS对象,移除内部代理结构 const plainUser = user.get({ plain: true }); // 直接把纯用户对象传给done,不额外包裹层级 return done(null, plainUser); } return done(null, false); } catch (error) { return done(error, false) } }) );
可选优化
你在控制器里判断用户类型的逻辑,返回状态码不合理:如果用户类型不符合要求,属于权限校验失败,应该返回403状态码,而不是代表服务端异常的500状态码:
const getPatientPsy = async (req, res) => { const authenticatedUser = req.user; // 此时authenticatedUser就是纯用户对象,可以直接访问userType属性 if (authenticatedUser.userType !== "patient") { res.status(403).send("权限不足,仅患者角色可访问该接口"); } }
内容的提问来源于stack exchange,提问作者Urco
相关产品推荐
相关产品推荐

