NodeJS+Express使用Passport+JWT认证时Mocha测试返回401问题求助
问题排查:Passport JWT认证测试返回401而非预期200
用户基于NodeJS+Express开发API,使用Passport和JWT实现接口认证功能,用Mocha配合Chai、chai-http做接口测试时,传入有效JWT请求需要认证的/team接口,预期返回200状态码,实际返回401。
测试文件代码
const passport = require ('passport'); // chai是一个第三方库,支持通过函数校验变量信息 const chai = require ('chai'); // chai插件,支持启动服务并向服务发起HTTP请求 const chaiHttp = require ('chai-http'); // 做接口测试需要引入后端入口文件app.js // .app用于获取服务实例对象 const app = require ('../app').app; // 调用use方法告知chai使用http插件 chai.use (chaiHttp); describe ('Auth Test Suite', () => { it ('用户未授权时返回401', (done) => { // 告知chai使用文件中定义的服务实例 chai.request (app) // 向服务发起指定类型的HTTP请求 .get ('/team') // 传入回调函数处理请求返回结果 .end ((err, res) => { // 校验返回的HTTP状态码符合预期 chai.assert.equal (res.status, 401); // 标记测试执行完成 done (); }); }); it ('JWT token有效时返回200', (done) => { // 告知chai使用文件中定义的服务实例 chai.request (app) // 向服务发起指定类型的HTTP请求 .post ('/login') // 传入回调函数处理请求返回结果 .end ((err, res) => { // 登录请求拿到token后,再次发起get请求校验token有效性 chai.request (app) .get ('/team') // 传入请求头用于JWT校验 .set ('Authorization', `jwt ${res.body.token}`) .end ((err, res) => { // token有效时预期返回200 chai.assert.equal (res.status, 200); // 标记测试执行完成 done (); }); }); }); });
app.js代码
// 导入express库 const express = require ('express'); // 导入passport库 const passport = require ('passport'); // 定义变量承载express的功能能力 const app = express (); // 定义express使用的监听端口 const port = 3000; // 调用认证校验逻辑 require ('./auth') (passport); // 调用app的listen方法启动服务监听连接,否则服务执行后会直接关闭,传入回调函数在服务启动后执行对应逻辑 app.listen (port, () => { console.log ('Server started on port' + port); }) // 挂载get请求处理逻辑,"req" 代表请求对象,"res" 代表响应对象 app.get ('/', (req, res) => { // 调用".send"方法向客户端返回请求响应 res.status (200) .send ('hello world'); }); app.post ('/login', (req, res) => { res.status (200) .json ( {token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0YjoxNTE2MjMXWF0IjoxNTE2MjMXWG2DMXM02MjMXH0IjoxNTE2MjMXWG2DMXM02MJMXH0IjoxNTE2MjMXMXWG2DMXMJ2MJMX2DXMXM02MjWG' ) }); app.get ('/team', // 访问该路由时会校验请求方是否持有有效JWT,为中间件 passport.authenticate ('jwt', {session: false}), (req, res) => { res.status (200) .send ('hello world'); }); app.post ('/team/pokemons', (req, res) => { res.status (200) .send ('hello world'); }); app.delete ('/team/pokemons/:pokeid', (req, res) => { res.status (200) .send ('hello world'); }); app.put ('/team', (req, res) => { res.status (200) .send ('hello world'); }); module.exports.app = app;
auth.js(Passport JWT配置)代码
// 导入passport相关库,Strategy用于创建认证策略 const jwtStrategy = require ('passport-jwt'). Strategy; const extractJwt = require ('passport-jwt'). ExtractJwt; // 导出接收passport作为参数的函数 module.exports = passport => { // 创建包含认证策略配置的JSON对象 const opts = { // 指定请求中JWT的提取位置 jwtFromRequest: extractJwt.fromAuthHeaderWithScheme ("JWT"), secretOrKey: 'secretPassword' } // 告知passport使用上述创建的策略,传入回调函数处理校验结果,执行自定义校验逻辑 passport.use (new jwtStrategy (opts, (decoded, done) => { console.log ('decoded jwt', decoded); return done (null, false); })); }
问题原因与修复方案
- 核心错误:JWT验证回调直接返回认证失败
auth配置中,passport-jwt的验证回调第二个参数传入false,代表直接判定认证不通过,这是401的直接原因。Passport要求验证通过时需要在第二个参数传入用户身份信息,而非false。
修复:将return done (null, false);修改为return done (null, decoded); - 次要错误:JWT令牌本身非法
login接口返回的token是无效拼接值,不符合JWT三段式格式规范,无法通过密钥校验。需要使用标准方法生成合法JWT:
首先安装JWT生成依赖:npm install jsonwebtoken
修改login接口代码:
const jwt = require('jsonwebtoken'); app.post ('/login', (req, res) => { const token = jwt.sign( {userId: 1, username: 'testUser'}, 'secretPassword', {expiresIn: '1h'} ) res.status(200).json({token: token}) });
- 可选优化:Authorization头格式统一
测试中使用小写jwt作为令牌前缀,配置中使用大写JWT作为scheme,虽然fromAuthHeaderWithScheme本身大小写不敏感,统一为JWT ${res.body.token}可以避免潜在兼容性问题。
修复后重新运行测试即可返回预期200状态码。
内容的提问来源于stack exchange,提问作者DDCruz
相关产品推荐
相关产品推荐

