Contents
  1. 1. 前言
  2. 2. 概述
  3. 3. 核心组件
  4. 4. Passport 初始化
  5. 5. Strategy
    1. 5.1. Local Strategy
    2. 5.2. Verify Callback
  6. 6. 认证
  7. 7. Sessions
    1. 7.1. session
    2. 7.2. 禁用 session
  8. 8. 权限控制
  9. 9. References

前言

本文是笔者所总结的有关 Nodejs Passport 系列之一,本篇文章主要是对其基本的构建原理进行梳理;

本文为作者原创作品,转载请注明出处;

概述

Passport 构建得及其的简单,它被设计为 Nodejs 的中间件;具体的使用过程是将 passport 作为中间件嵌入到某 Express 的请求之前或者之后,用来验证用户的身份;而针对不同的验证方式,Passport 通过提供了不同的验证策略(Strategies),通过这些不同的 Strategies 来提供不同的验证逻辑和方式,这些 Strategies 在 Passport 中被定义成不同的模块通过依赖包的形式进行载入,并作为 Passport 的中间件,在需要的时候注入;常用的 Strategies 有,

核心组件

nodejs passport component diagram.png

上图以 Local Strategy 和 Basic Strategy 为例,描绘了 passport 所相关的核心组件;可以看到,passport 最主要的就是两大模块,一个是 passport 自身,另外一个就是 Strategy 策略;

  1. 首先,要将需要使用到的 Strategy 注册到 passport 对象中,这一步的关键是,提供回调方法接口给用户,使得用户可以自定义扩展的能力;那么用户如何实现扩展呢?看下面一个简单的例子,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    var passport = require('passport')
    , LocalStrategy = require('passport-local').Strategy;
    passport.use(new LocalStrategy(
    function(username, password, done) {
    User.findOne({ username: username }, function (err, user) {
    if (err) { return done(err); }
    if (!user) {
    return done(null, false, { message: 'Incorrect username.' });
    }
    if (!user.validPassword(password)) {
    return done(null, false, { message: 'Incorrect password.' });
    }
    return done(null, user);
    });
    }
    ));

    首先,加载 passport-local 模块所暴露的 Strategy 对象,
    然后,通过 passport.use 方法引入一个用户自定义的 LocalStrategy 实例,通过用户所实现的回调方法来实现自定义,在该回调方法中,代码第 6 到 15 行,通过用户自定义的 User.findOne 方法来根据 username 进行查找;

  2. passport 对象通过调用方法 authenticate(identifier, callback) 来对用户身份进行认证;来看一个最简单的例子,

    1
    2
    3
    app.post('/login',
    passport.authenticate('local', { successRedirect: '/',
    failureRedirect: '/login' }));

    可以看到,passport.authenticate 方法中的 identifier 是一个字符串 'local',该字符串对应的就是 Local Strategy 实例,表示通过 Local Strategy 来对用户的身份进行认证;如果成功则返回首页,不成功则跳转回 login 页面;

Passport 初始化

Passport 作为 Express 的中间件被 Express 所使用,那么该中间件该如何初始化并注入到 Express 实例中呢?下面是使用 Express 4.x 的情况,

1
2
3
4
5
6
7
8
9
var passport = require('passport'),
session = require("express-session"),
bodyParser = require("body-parser");
app.use(express.static("public"));
app.use(session({ secret: "cats" }));
app.use(bodyParser.urlencoded({ extended: false }));
app.use(passport.initialize());
app.use(passport.session());

可见,通过 app.use 方法将 passport 实例注入到 Express 实例 app 中,要注意的是,

Strategy

Local Strategy

Strategy 是 passport 的一个核心模块,用来实现不同的验证方式,本小节笔者主要描述如何配置和使用 Local Strategy;

  1. 首先安装 Local Strategy 模块包,

    1
    $ npm install passport-local
  2. 通过 passport.use 方法引入用户自定义的 Local Strategy 模块,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    var passport = require('passport')
    , LocalStrategy = require('passport-local').Strategy;
    passport.use(new LocalStrategy(
    function(username, password, done) {
    User.findOne({ username: username }, function(err, user) {
    if (err) { return done(err); }
    if (!user) {
    return done(null, false, { message: 'Incorrect username.' });
    }
    if (!user.validPassword(password)) {
    return done(null, false, { message: 'Incorrect password.' });
    }
    return done(null, user);
    });
    }
    ));

    通过用户自定义的回调方法来初始化 LocalStrategy 实例,后续的认证过程中,将会使用用户自定逻辑来查找用户是否存在;注意,默认情况下,使用 'username' 来进行查找,如果想要指定成其它的字段,使用如下的方式,

    1
    2
    3
    4
    5
    6
    7
    8
    passport.use(new LocalStrategy({
    usernameField: 'email',
    passwordField: 'passwd'
    },
    function(username, password, done) {
    // ...
    }
    ));

这样,我们就配置好了我们本地的 passport 实例,只是该实例只支持通过 Local Strategy 对用户的身份进行认证;那么,该 passport 实例是如何作为 Express 的中间件执行的呢?看下一小节,

Verify Callback

用户自定义的回调方法中,要能够通过一种有效的方式通知 passport,让它知道用户的认证是成功还是失败了,这里是通过 verify callback 方法 done() 来实现的,

  1. 如果用户认证成功,通过如下的方式通知 passport 用户已经认证成功了,

    1
    return done(null, user);
  2. 如果认证失败,通过如下的方式通知 passport 用户认证失败了,

    1
    return done(null, false);
  3. 如果需要明确失败的原因,

    1
    return done(null, false, { message: 'Incorrect password.' });

认证

通过上一章节配置好了 passport 实例以后,那么我们该如何将该使用了 Local Strategy 的 passport 实例应用到 Express 的实例上呢?很简单,

1
2
3
4
5
app.post('/login',
passport.authenticate('local', { successRedirect: '/',
failureRedirect: '/login',
failureFlash: true })
);

将 passport.authenticate 方法作为 Express app 实例的 /login router 的 handler 就好了,并且定义了相应的验证成功或者失败的 redirect 规则;要注意的是,如果认证成功,用户 user 将会被保存在对象 req.user 中,不过要注意两种场景,

  1. 启用 session
    如果启用了 session,已认证的用户的后续请求将可以直接通过 req.user 获取用户的身份信息,并且不再需要再次认证了;不过这得益于 session 的用户序列化的机制,参考 session 章节;
  2. 禁用 session
    这种情况下,req.user 只在当前已认证的请求中有效,下次请求仍然需要再次认证,req.user 需要再次被填充;也就是每次认证通过以后进行填充;

Sessions

session

默认情况下,当用户身份认证成功以后,passport 会开启一个 session 来维持登录用户的身份状态;session 通过一个 Session ID 保存在服务器端和用户的浏览器端,这样来维持该用户的登录的身份状态,那么现在的问题是,passport 是如何维持该用户的身份状态的呢?在服务器端,为了尽量减少内存的占用,在 session 中只会保存用户的 ID,当已认证的用户再次发起某个请求以后,当服务器端调用 req.user 的时候,会通过 passport.deserializeUser() 根据用户的 ID 加载 user,这样既可做到内存占用的最小化以及需要的时候,才会从数据库中加载用户的信息;为了然上述的方式得以生效,我们需要为 user 添加有关序列化的配置代码,

  • 向 session 中写入 user ID,对应的是 serialize 流程,

    1
    2
    3
    passport.serializeUser(function(user, done) {
    done(null, user.id);
    });
  • 向 session 中载入 user,对应的是 deserialize 流程,

    1
    2
    3
    4
    5
    passport.deserializeUser(function(id, done) {
    User.findById(id, function(err, user) {
    done(err, user);
    });
    });

    备注,如果禁用了 session,在 authenticate 方法验证成功以后,user 是不是也是通过上述的方式写入 req.user 的?需要验证一下…

禁用 session

同样,我们可以显式的为某个请求指定不使用 session,比如,

1
2
3
4
5
app.get('/api/users/me',
passport.authenticate('basic', { session: false }),
function(req, res) {
res.json({ id: req.user.id, username: req.user.username });
});

设置 passport.authenticate 方法的第二个参数设置为 session = false 即可;注意,这样设置以后,便不会再有 session 的特性了,那么每次新的请求都需要对用户的身份进行重新验证,并加载用户(用户的身份信息将会被保存到 req.user 实例中);

权限控制

遗憾的是,passport 只负责对用户进行认证,它并不会进行权限的控制,也就是说,哪些权限( Role )可以访问到哪些资源;这部分需要其它的解决方案了!

References

http://www.passportjs.org/docs/

Contents
  1. 1. 前言
  2. 2. 概述
  3. 3. 核心组件
  4. 4. Passport 初始化
  5. 5. Strategy
    1. 5.1. Local Strategy
    2. 5.2. Verify Callback
  6. 6. 认证
  7. 7. Sessions
    1. 7.1. session
    2. 7.2. 禁用 session
  8. 8. 权限控制
  9. 9. References