使用passport管理第三方授权认证

来源:互联网 发布:师傅徒弟知错了txt 编辑:程序博客网 时间:2024/05/20 06:30

passport是一个为Nodejs设计的,兼容Express的认证中间件。通过第三方插件的形式(以下称为strategy),可以应对各式各样的认证请求。passport具有高度的灵活性,并不依赖于任何一个路由,或者指定的数据存储,这样给上层开发者提供的极大的便利性。passport提供的接口也相对简单,只需要给它一个认证请求,passport会提供一个钩子函数(hook)告诉你请求失败了或者成功了。


1. passport的设计思路

passport基于一个高度灵活的设计结构,主要由四个模块组成:passport插件(strategy)管理模块、执行认证操作的授权模块framewark适配模块session管理模块。其结构框图如下:

系统框图

授权流程(引用github oath2.0来举例)

授权流程

1.授权插件体系

所有的passport插件都继承自passport-strategy,紧接着根据不同的授权流程又细分为OAuth2.0授权中间件、OPENID类的授权中间件、以及帐号密码类的授权中间件。这些基本的中间件官方已经有了标准的实现。开发者需要做的是去继承这些授权中间件,针对不同的业务需求,配置特定的部分参数。就可以完成一整个授权的流程。常见的OAth2.0标准的授权插件有:passport-githubpassport-twitter。这些针对不同厂商的第三方Strategy体量都非常小,基本在100行代码内可以搞定,因为大部分工作都交给标准的strategy来做了。

2.framework适配

passport官方的实现基于标准的Express形式的风格,也就是说,中间件的函数风格类似于:

function authenticate(req, res, next)

如果需要适配其他的框架需要实现特定风格的authenticate函数。

3. session管理

passport提供session的功能,如果开启该功能,则需要提供序列化,以及反序列化接口。express-session是一个很好的session管理包,所以还是让passport专注于授权认证吧!


2. passport使用

1. 注入第三方strategy

function verifyProfile(accessToken, refreshToken, profile, done) {  profile.accessToken = accessToken;   done(null, profile);}passport.use(new GithubStrategy({clientID: '123-456-789',clientSecret: 'easdasjdklasjd',callbackURL: 'http://www.example.com/auth/github/callback'}), verifyProfile);

passport提供use函数用于注入授权认证的插件、unuse用于注销认证插件。verifyProfile接口是一个hook,用来验证用户信息的有效性。

2. 授权认证

//请求授权码app.get('/user/login/github', passport.authenricate('github', {scope: 'wl_scope'}));//获取accessToken以及请求用户信息,关闭session功能app.get('/auth/github/callback', passport.authenricate('github', {session: false, failureRedirect: '/'}));

3. passport源码分析

1. 授权认证入口函数

Authenticator.prototype.authenticate = function(strategy, options, callback) {  return this._framework.authenticate(this, strategy, options, callback);};

2. express 风格的认证函数

module.exports = function authenticate(passport, name, options, callback) {  if (typeof options == 'function') {    callback = options;    options = {};  }  return function authenticate(req, res, next) {     function allFailed() {      if (callback) {        if (!multi) {          return callback(null, false, failures[0].challenge, failures[0].status);        } else {          var challenges = failures.map(function(f) { return f.challenge; });          var statuses = failures.map(function(f) { return f.status; });          return callback(null, false, challenges, statuses);        }      }      ....    }    (function attempt(i) {      var layer = name[i];      // If no more strategies exist in the chain, authentication has failed.      if (!layer) { return allFailed(); }      var prototype = passport._strategy(layer);      if (!prototype) { return next(new Error('Unknown authentication strategy "' + layer + '"')); }      var strategy = Object.create(prototype);      strategy.success = function(user, info) {        if (callback) {          return callback(null, user, info);        }        ....         req.logIn(user, options, function(err) {          if (err) { return next(err); }          function complete() {            if (options.successReturnToOrRedirect) {              var url = options.successReturnToOrRedirect;              if (req.session && req.session.returnTo) {                url = req.session.returnTo;                delete req.session.returnTo;              }              return res.redirect(url);            }            if (options.successRedirect) {              return res.redirect(options.successRedirect);            }            next();          }          complete();      }       ....     //调用oauth2.0认证函数     strategy.authenticate(req, options);  })(0);

3. Oauth2.0 认证函数

OAuth2Strategy.prototype.authenticate = function(req, options) {  options = options || {};  var self = this;  if (req.query && req.query.error) {    if (req.query.error == 'access_denied') {      return this.fail({ message: req.query.error_description });    } else {      return this.error(new AuthorizationError(req.query.error_description, req.query.error, req.query.error_uri));    }  }  var callbackURL = options.callbackURL || this._callbackURL;  if (callbackURL) {    var parsed = url.parse(callbackURL);    if (!parsed.protocol) {      // The callback URL is relative, resolve a fully qualified URL from the      // URL of the originating request.      callbackURL = url.resolve(utils.originalURL(req, { proxy: this._trustProxy }), callbackURL);    }  }  if (req.query && req.query.code) {   //根据授权码获取accessToken以及用户信息    var code = req.query.code;    var params = this.tokenParams(options);    params.grant_type = 'authorization_code';    params.redirect_uri = callbackURL;    this._oauth2.getOAuthAccessToken(code, params,      function(err, accessToken, refreshToken, params) {        if (err) { return self.error(self._createOAuthError('Failed to obtain access token', err)); }        self._loadUserProfile(accessToken, function(err, profile) {          if (err) { return self.error(err); }          function verified(err, user, info) {            if (err) { return self.error(err); }            if (!user) { return self.fail(info); }            self.success(user, info);          }          try {            //_verify 就是注入插件的时候指定的用户信息校验函数只有通过该函数校验,认证才算完成,用户信息被`success`挂在req.user。            if (self._passReqToCallback) {              var arity = self._verify.length;              if (arity == 6) {                self._verify(req, accessToken, refreshToken, params, profile, verified);              } else { // arity == 5                self._verify(req, accessToken, refreshToken, profile, verified);              }            } else {              var arity = self._verify.length;              if (arity == 5) {                self._verify(accessToken, refreshToken, params, profile, verified);              } else { // arity == 4                self._verify(accessToken, refreshToken, profile, verified);              }            }          } catch (ex) {            return self.error(ex);          }        });      }    );  } else {   //获取授权码    var params = this.authorizationParams(options);    params.response_type = 'code';    params.redirect_uri = callbackURL;    var scope = options.scope || this._scope;    if (scope) {      if (Array.isArray(scope)) { scope = scope.join(this._scopeSeparator); }      params.scope = scope;    }    var location = this._oauth2.getAuthorizeUrl(params);    this.redirect(location);  }};

4. 获取用户信息

OAuth2Strategy.prototype._loadUserProfile = function(accessToken, done) {  var self = this;  function loadIt() {    return self.userProfile(accessToken, done);  }  function skipIt() {    return done(null);  }  //通过_skipUserProfile参数跳过不需要获取用户信息的认证。  if (typeof this._skipUserProfile == 'function' && this._skipUserProfile.length > 1) {    // async    this._skipUserProfile(accessToken, function(err, skip) {      if (err) { return done(err); }      if (!skip) { return loadIt(); }      return skipIt();    });  } else {    var skip = (typeof this._skipUserProfile == 'function') ? this._skipUserProfile() : this._skipUserProfile;    if (!skip) { return loadIt(); }    return skipIt();  }};//第三方插件需要重载该接口实现用户信息的获取,`passport`把该接口完全开放给开发者。OAuth2Strategy.prototype.userProfile = function(accessToken, done) {  return done(null, {});};

4. 实现自己的strategy实现

实现自己的第三方passport-strategy主要有三个步骤:

1. 继承passport-oauth2

function Strategy(options, verify) {  options = options || {};  options.authorizationURL = options.authorizationURL || 'https://github.com/login/oauth/authorize';  options.tokenURL = options.tokenURL || 'https://github.com/login/oauth/access_token';  options.scopeSeparator = options.scopeSeparator || ',';  options.customHeaders = options.customHeaders || {};  if (!options.customHeaders['User-Agent']) {    options.customHeaders['User-Agent'] = options.userAgent || 'passport-github';  }  OAuth2Strategy.call(this, options, verify);  this.name = 'github';  this._userProfileURL = options.userProfileURL || 'https://api.github.com/user';  this._oauth2.useAuthorizationHeaderforGET(true);}/** * Inherit from `OAuth2Strategy`. */util.inherits(Strategy, OAuth2Strategy);

2. 实现获取用户信息接口

Strategy.prototype.userProfile = function(accessToken, done) {  this._oauth2.get(this._userProfileURL, accessToken, function (err, body, res) {    var json;    if (err) {      return done(new InternalOAuthError('Failed to fetch user profile', err));    }    try {      json = JSON.parse(body);    } catch (ex) {      return done(new Error('Failed to parse user profile'));    }    var profile = Profile.parse(json);    profile.provider  = 'github';    profile._raw = body;    profile._json = json;    done(null, profile);  });}

3. 解析用户信息函数实现

var parse = function(json) {  if ('string' == typeof json) {    json = JSON.parse(json);  }  var profile = {};  profile.id = String(json.id);  profile.displayName = json.name;  profile.username = json.login;  profile.profileUrl = json.html_url;  if (json.email) {    profile.emails = [{ value: json.email }];  }  return profile;};

只需要三步骤,轻松实现授权认证!,passport官网已经有非常多的第三方认证实现,为了避免重复造轮子,请先查找是否有你要的插件: Search Strategy

0 0
原创粉丝点击