美文网首页
“真实世界”全栈开发-3.4-发放令牌的中间件

“真实世界”全栈开发-3.4-发放令牌的中间件

作者: 桥头堡2015 | 来源:发表于2018-02-05 11:39 被阅读15次

上一讲创建了用户模型。这一讲,在创建对应用户注册和登录等等操作的端点之前,我们先要创建负责用户身份验证的(也就是与JWT相关的)Express中间件。

什么是Express中间件?如果我们把Express应用比作一座乐高层叠而成的高塔,这座塔接受HTTP请求的对象,经过每一层逻辑的处理之后返回一个HTTP响应的对象,那么一个中间件就是其中的一层,代表了后端应用的一小段逻辑。感兴趣的读者可以阅读我的这篇博文,详细理解下Express的中间件。

后端涉及到令牌的逻辑可以分为两类:发放令牌的逻辑和验证令牌的逻辑;对应的中间件也分为这么两类。本文先讲如何创建及配置发放令牌的中间件。验证的部分,留到下一讲。

创建发放JWT令牌的中间件

我们使用Passport这个库来帮助我们检查用户的身份。这个库是专为Node开发的验证系统,提供了很多验证策略,我们将会使用所谓的“本地验证”(Local Authentication)策略。“本地”是相对于QQ、微信、微博等等第三方验证来说的,说白了也就是考察用户名(或者等同于用户名的邮箱等)和密码两条信息。

配置Passport

新建config/passport.js文件,写入:

var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var mongoose = require('mongoose');
var User = mongoose.model('User');

passport.use(new LocalStrategy({
  usernameField: 'user[email]',
  passwordField: 'user[password]'
}, function(email, password, done) {
  User.findOne({email: email}).then(function(user){
    if (user && user.validPassword(password))
      return done(null, user);
    return done(null, false, {errors: {'email or password': 'is invalid'}});
  }).catch(done);
}));

上面的配置代码里,passport对象使用一个LocalStrategy对象(来自passport-local库)。这个对象代表了本地验证的逻辑。不过,我们的用户在登录时不是提供用户名,而是邮箱和密码,类似于:

{
  "user": {
    "email": "jake@example.com".
    "password": "mypasswordisjake"
  }
}

所以我们用下面的配置来告诉这个策略对象怎么去从这样一个JSON对象中抽取对应的字段信息:

{
  usernameField: 'user[email]',
  passwordField: 'user[password]'
}

抽取出来的信息会传给后面的回调函数。这个回调函数首先检查数据库中是否有与传入的邮箱对应的用户,然后检查密码是否正确。注意done这个参数,它实际上也是一个回调函数,其签名为(err, user, info)=>void。在后面使用这个passport对象时,我们需要传入这么个回调来处理验证用户名/密码的结果(成功则发放令牌,不成功则返回错误信息;具体实现见后文)。

在应用里初始化上述的passport对象

app.js里加入如下一行:

require('./models/User');
// +++
require('./config/passport');
// +++

app.use(require('./routes'));

使用passport的中间件

在应用中创建了passport对象,现在我们要创建使用这个对象的中间件。

新建routes/api/users.js文件,写入下面的代码:

const passport = require('passport');

const localAuthentication = (req, res, next) => {
  passport.authenticate('local', {session: false}, (err, user, info) => {
    if (err) return next(err);

    if (!user)
      return res.status(422).json(info);
    user.token = user.generateJWT();
    return res.json({user: user.toAuthJSON()});
  } (res, req, next);
}

从签名可以看出localAuthentication就是一个典型的Express中间件。(再强调一遍,一般的不处理错误的Express中间件,简单来说,就是签名为(req, res, next)=>void的函数,其三个参数分别代表请求、响应和逻辑栈中的下一个中间件。)事实上,passport.authenticate生成的也是这么一个函数,只不过为了从外部获得res参数,所以我们在外面包了一层。

passport.authenticate方法接受三个参数。

第一个local表明所用的是本地验证。

Passport默认的验证方式是基于session的,第二个参数则是关闭这种行为。

第三个参数(err, user, info) => {...}就对应上一节提到的done回调,是处理验证结果的逻辑的具体实现:如果验证过程中出现错误,则调用后面的一个处理错误的中间件next(err);如果验证失败,则回复422;如果验证成功,则返回带有令牌的用户JSON对象。

从上面的代码我们可以看到使用Passport的好处:它为各种验证方式提供了一致而好用的接口,我们只用关心passport对象的配置和怎么处理验证结果,而不用去处理验证时的各种烦琐的细节。以后如果我们想要使用第三方登录,可以很方便的实现。

现在唯一美中不足的地方是我们没有遵循之前“真实世界”全栈开发-2.2-错误处理与注册登录API中的规定:将错误发生的字域和错误信息配为键值对。万一前端传来的请求体中不包含邮箱或者密码,我们应该向前端准确地指出缺少的到底是哪个字段,而且我们最好在Passport做验证之前就处理这种信息缺失的错误。

routes/api/users.js里,把上面的代码改写为:

const localAuthentication = (req, res, next) => {
  if(!req.body.user.email)
    return res.status(422).json({errors: {email: "can't be blank"}});

  if(!req.body.user.password)
    return res.status(422).json({errors: {password: "can't be blank"}});

  passport.authenticate('local', {session: false}, (err, user, info) => {
    if (err) return next(err);

    if (!user)
      return res.status(422).json(info);
    user.token = user.generateJWT();
    return res.json({user: user.toAuthJSON()});
  }) (req, res, next);
}

上面增加了确保请求体中含有邮箱和密码数据的逻辑,返回的错误信息符合之前的规定。

中间件定义好之后,还需要把它插入到Express应用的中间件栈中,这一步我们留给后面的章节。下一讲我们实现负责验证令牌的中间件。

相关文章

网友评论

      本文标题:“真实世界”全栈开发-3.4-发放令牌的中间件

      本文链接:https://www.haomeiwen.com/subject/gibmzxtx.html