如何测试使用 JWT 身份验证的节点 API(使用用户登录获取令牌)

Posted

技术标签:

【中文标题】如何测试使用 JWT 身份验证的节点 API(使用用户登录获取令牌)【英文标题】:How to test a Node API that uses JWT Authentication (with User login to get token) 【发布时间】:2016-09-14 01:17:48 【问题描述】:

TL;DR - 在使用 JWT 进行身份验证且令牌本身仅授予用户名/密码登录的节点 API (Express) 中测试资源的方法是什么?

我对测试有点陌生,想得到一些建议。最终目标是拥有一个经过全面测试的 API,然后开始学习如何将其连接到持续集成解决方案。

正在使用的技术

我使用 ExpressNode 中编写了一个 API。 Mongo 是数据库。 Mongoose 用作 ODM。 jsonwebtoken 包用于创建/验证令牌。 Passport 用于在路由上轻松添加用户身份验证作为 Express 中间件。

API 信息

API 有各种资源——其中的细节对于这个查询并不重要,但为了简单起见,我们假设它是无处不在的 Todo 应用程序。

保存在数据库中的每个资源都与单个用户相关联。

API 使用 JWT 跨各种资源端点进行身份验证。令牌本身包含针对资源存储在 Mongo 数据库中的唯一用户 ID。要获取令牌本身需要用户先注册(返回令牌)然后登录以获取新令牌。

假装代码。

我将简化下面的代码,不使用任何环境配置等...

app.js

var express = require('express');
var app = express();
var mongoose = require('mongoose');
var bodyParser = require('body-parser');
var passport = require('passport');

mongoose.connect('mongodb://localhost/somedatabasename');

app.set('port', process.env.PORT || 3000);
app.use(bodyParser.urlencoded( extended: true ));
app.use(bodyParser.json());

app.use(passport.initialize());
// ... Passport JWT Strategy goes here - omitted for simplicity ...

var userRouter = require('./api/users/routes');
app.use('/users', userRouter);
var todoRouter = require('./api/todos/routes');
app.use('/todos', todoRouter);

app.listen(app.get('port'), function() 
  console.log('App now running on http://localhost:' + app.get('port'));
);

./api/todos/routes.js

var router = require('express').Router();
var controller = require('./controller');
var passport = require('passport');

router.route('/')
  .all(passport.authenticate('jwt',  session: false))
  .get(controller.getAll)
  .post(controller.create);

router.route('/:id')
  .all(passport.authenticate('jwt',  session: false))
  .get(controller.getOne)
  .put(controller.update)
  .delete(controller.delete);

module.exports = router;

./api/users/routes.js

var router = require('express').Router();
var controller = require('./controller');
var passport = require('passport');

router.route('/')
  // User signup
  .post(controller.create);

router.route('/me')
  // User Login
  .post(passport.authenticate('local',  session: false), controller.login)
  // Get current user's data
  .get(passport.authenticate('jwt',  session: false), controller.getOne)
  // Update current user's data
  .put(passport.authenticate('jwt',  session: false), controller.update)
  // Delete current user
  .delete(passport.authenticate('jwt',  session: false), controller.delete);

module.exports = router;

./api/users/model.js

var mongoose = require('mongoose');
var bcrypt = require('bcrypt');

var UserSchema = new mongoose.Schema(
  username: 
    type: String,
    required: true,
    unique: true
  ,
  password: 
    type: String,
    required: true
  
);

// ... for simplicity imagine methods here to
// - hash passwords on a pre save hook using bcrypt
// - compare passwords using bcrypt when logging in

module.exports = mongoose.model('User', UserSchema);

./api/todos/model.js

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var momentSchema = new Schema(
  title: 
    type: String
  ,

  // Bunch of other fields here...

  _user: 
    type: Schema.Types.ObjectId,
    ref: 'User'
  
);

module.exports = mongoose.model('Moment', momentSchema);

我省略了一些示例代码以保持简洁。

例如,用户的控制器将包含模型,其功能将:

controller.create - 注册新用户(返回令牌) controller.login - 在 Passport Local 确认用户名/密码组合后返回有效令牌 controller.getOne - 根据从 JWT 令牌中检索到的用户 ID,使用 Mongoose 从 Mongo 返回用户数据。 controller.update - 使用 Mongoose 在 Mongo 中更新用户数据 controller.delete - 使用 Mongoose 删除用户在 Mongo 中的数据

Todo 的 控制器会做类似的事情 - 只是通过 Mongoose 与 Mongo 数据交互,但查询将始终包含用户 ID 以将特定(例如)Todo 项目与经过身份验证的用户(经过身份验证通过智威汤逊)。

测试难题

我将如何使用 Mocha、Chai 和 SuperTest 的组合来测试这样的东西?

我会:

在 Mongo 中创建一个测试数据库并且测试中的连接字符串是否不同?这意味着保存存储在数据库中的实际数据以供测试。 以某种方式模拟数据而不使用测试数据库?但是如何处理用户保存/登录以检索令牌?

在开发时与使用某些 CI 工具进行部署时(我什至在我的研究中还没有涉及到的东西)进行测试时,测试在本地如何工作?

任何帮助将不胜感激,我希望我已经为上面的虚拟数据/代码提供了足够的信息:/

【问题讨论】:

嗨!你想通了吗?我也在尝试实现这种测试。 【参考方案1】:

在测试期间,您通常会模拟您的 mongo DB(类似于mongo-mock。这样,您不需要运行实际的数据库来运行测试(您不是在测试数据库,而是在测试您的代码)。

在测试期间,您可以将mongodb 替换为mongo-mock,然后运行您的测试。要获取您的令牌,您需要使用有效的模拟凭据发布到您的 /me URL,该端点将返回该令牌,然后您将在下次调用时使用它来测试您的另一个端点。

在令牌方面,我通常在请求开始时检查它,然后再进入其他端点。 (我没有使用护照,但想法是):

app.use(validate_jwt_middleware);
app.use('/users', userRouter);

这样,如果令牌无效,它对整个站点都无效,而不仅仅是您的部分。

另外,我使用的不是 SuperTest,而是 chai-http,所以我无法帮助您具体说明。

希望这会有所帮助,

【讨论】:

以上是关于如何测试使用 JWT 身份验证的节点 API(使用用户登录获取令牌)的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Postman 测试 jwt 身份验证

如何在 vue 中存储、管理 REST API JWT 身份验证令牌?

我如何模拟 auth0 身份验证以进行测试?

如何使用 JWT 和 Passport 正确使用 nodeJs API 的身份验证?

使用节点 js、AngularJs 和 JWT 使用用户名和密码进行身份验证

Angular:如何通过 API 调用使用 JWT 身份验证