passport 中间件学习

Passportjs: nodejs 身份验证中间件

本文资料来源

  1. passport官网

  2. passport strategy

  3. 伤神的博客

  4. 知乎-吴成琦

一、Passport介绍

Passport 是 基于 Express框架的Node.js 的身份验证中间件。 Passport 极其灵活和模块化,可以不显眼地放入任何基于 Express 的 Web 应用程序中。

认证请求流程(以配置localStrategy为例):

当我们通过 接口 /login 进行身份验证时候,发生以下情况:

    1. 发起 /login 请求时,执行我们设置好的 passport.authenticate
    1. 开始进行认证,因为我们配置的是 localStrategy , 所以passport 将调用 localStrategy 进行逻辑认证;
    1. Passport 获取到 请求中的usernamepassword(一般是放在body中,在这里我放在了路径里方便查看),将其传递给策略中的verification function

    1. 加载用户传过来的数据并检查是否符合我们的判定条件
    1. 判定条件结果:
    • 判定条件未执行完成:比如在验证的逻辑中出现数据库查询失败,调用其他服务报错等等,调用 done(err),将错误信息抛出;
    • 数据不符合判定条件:当用户输入的参数不符合判定条件,调用 done(null, false)
    • 符合判定逻辑:验证通过,调用 done(null, user)
    1. 如果 5 验证成功,passport.authenticate() 自动调用 req.login() 函数,该函数主要在用户注册时使用,在此期间可以调用 req.login() 来自动记录新注册的用户。

    1. 之后(验证通过)继续调用 passport.serializeUser 方法(之前定义好的),将用户信息序列化,目的放到 req.session.passport.user
    1. 用户信息最终也会放到 req.user
    1. 流程结束

后续经过身份验证的请求流程

在用户登录之后,用户访问被保护的路由时,会经过如下的流程:

    1. express 框架加载 session 数据,由于之前登录的用户信息已经被passport 保存在了session中,所以可以从req.session.passport.user 中找到
    1. 这个时候 之前定义好的passport.initialize 函数就会被调用,如果找到了之前认证过的session信息,就会将passport.user 也放到req中;如果请求没有经过认证,就将 passport.user = {} 放到req中
    1. 接下来,passport.session就会被触发(每一个请求都会触发这个中间件),逻辑: 如果该中间件在请求中找到了序列化的用户信息,那么这个请求就是已经经过认证了的;
    1. passport.session 会回调之前定义好的 passport.deserializeUser ,将反序列化的用户对象放到req.user 中

Passport 的方法介绍

  • passport.initialize : 每个请求进来都会被调用,可以确保session 中有 passport.user(用户信息 || {} )
  • passport.session:passport.session 中间件是一个 Passport 策略,如果服务端找到序列化的用户信息,它将序列化的用户信息加载到 req.user 上
  • passport.deserializeUser: 每个请求都会通过 passport.session 调用该函数,用户信息或者其他的信息可以通过这个函数加载到req.user 上
  • 策略什么时候调用: 只有在路由中使用 了 passport.authenticate 中间件才会调用
  • passport.serializeUser 只会在验证期间才会调用,以此来存储对应的用户信息

其他的一些方法

  • req.login()
  • req.logout()
  • req.isAuthenticated()
  • req.isUnAuthenticated()

策略示例

  1. passport-wechat
const passport = require('passport');

const WechatStrategy = require('passport-wechat');

passport.use(new WechatStrategy({
    appID:'', // {APPID},
    name:'wechat-test-demo', // {默认为wechat,可以设置组件的名字}
    appSecret:'', // {APPSECRET},
    client:'', // {wechat|web},
    callbackURL:'/emma/wechat/callback', // {CALLBACKURL},
    scope:'', // {snsapi_userinfo|snsapi_base},
    state:'', // {STATE},
    getToken:'', // {getToken},
    saveToken:'', // {saveToken}
  }, function(accessToken, refreshToken, profile, done) {
    return done(err,profile);
  }
));

router.get('/emma/wechat', passport.authenticate('wechat-test-demo', passportOption));
router.get('/emma/wechat/callback', passport.authenticate('wechat-test-demo', {
    failureRedirect: '/auth/fail',
    successReturnToOrRedirect: '/',
}));

passportOption

state —— 重定向后会带上 state 参数,开发者可以填写a-zA-Z0-9的参数值,最多128字节
callbackURL —— 授权后重定向的回调链接地址, 需要使用 urlEncode 对链接进行处理
scope —— snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid),snsapi_userinfo (弹出授权页面,可通过 openid 拿到昵称、性别、所在地。并且, 即使在未关注的情况下,只要用户授权,也能获取其信息 )
  1. passport-oauth2
const OAuth2Strategy = require('passport-oauth2');

passport.use(new OAuth2Strategy({
    authorizationURL: 'https://divineswordvilla.xyz/oauth2/authorize',
    tokenURL: 'https://divineswordvilla.xyz/oauth2/token',
    clientID: EXAMPLE_CLIENT_ID,
    clientSecret: EXAMPLE_CLIENT_SECRET,
    callbackURL: "http://divineswordvilla.xyz/auth/emma/callback"
},function(accessToken, refreshToken, profile, cb) {
    // 业务逻辑处理
    if (err) return cb(err);
    return cb(null, profile);
}));

router.get('/emma/login', passport.authenticate('oauth2'));
router.get('/auth/emma/callback', passport.authenticate('oauth2', { failureRedirect: '/login' }), function (req, res) {
    // 认证成功之后,跳转到业务需求的界面
    res.redirect('/');
});
  1. passport-jwt
    使用JSON web token (jwt) 对端进行认证,可用于一些没有用session的restful 端点
const passport = require('passport');
const JwtStrategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;

passport.use(new JwtStrategy({
    jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
    secretOrKey: 'secret', 
    name: '', // {}
    issuer: '',
    audience: '',
}, function(jwt_payload, done) {
    // 根据物业需求添加逻辑
    User.findOne({id: jwt_payload.sub}, function(err, user) {
        if (err) {
            return done(err, false);
        }
        if (user) {
            return done(null, user);
        } else {
            return done(null, false);
        }
    });
}));

app.post('/profile', passport.authenticate('jwt', { session: false }),
    function(req, res) {
        res.send(req.user.profile);
    }
);
  1. passport-local
const passport = require('passport');
const 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); }
            if (!user.verifyPassword(password)) { return done(null, false); }
            return done(null, user);
          });
    }
));

app.post('/login', passport.authenticate('local', { failureRedirect: '/login' }),
  function(req, res) {
    // 业务逻辑
    res.redirect('/');
});

遗留的问题

  • initialize 和 策略 的先后顺序是否会对 passport的使用有影响?
  • 当不符合验证(符合验证) 会出现一些什么信息
  • 登录完之后,其他请求进入为完整的200;
  • 受保护的路由不传用户名密码也能返回200
  • 受保护的路由,在验证失败或者过期之后返回 401
  • 序列化反序列化目的?
  • 序列化反序列化使用场景?
  • 什么时候会执行序列化和反序列化函数?
  • name 用法
    demo
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容