egg-passport 使用 和源碼 分析

寫教程的環(huán)境

  "dependencies": {
    "egg": "^2.26.0",
    "egg-passport": "^2.1.1",
}

egg-passport 其實就是封裝的 passport
passport官方文檔

passport 代碼核心就是簡單驗證遣铝。 而驗證規(guī)則 則是通過 單獨 的 策略佑刷。eggjs 通過注入 app 實例,掛載 egg-passport 插件酿炸。然后通過 app.passport.use(Strategy) 啟用策略瘫絮,而 use 函數(shù) 是通過 super()繼承 而來。

ps: 框架 自帶 一個 session策略填硕,用來持續(xù)化存取用戶信息麦萤。

代碼順序

  1. 在 plugin.js 添加 egg-passport 插件,并打開扁眯。

  2. 在 router.js 添加 需要驗證的地址
    router.post('/login', app.passport.authenticate('local', { successRedirect: '/authCallback' }));

  3. 新建 app.js 引入 passport壮莹,添加驗證規(guī)則,這樣程序就會加入到 passport 的私有數(shù)組姻檀, _verifyHooks命满。存起來

  app.passport.verify(async (ctx, user) => {
    // ctx.logger.error(new Error('whoops'));

    ctx.logger.debug('debug info');

    return user
  });
  1. 實例化我們的策略
const LocalStrategy = require('passport-local').Strategy;

  app.passport.use(new LocalStrategy({
    passReqToCallback: true, // passReqToCallback 意思是 在下面的 回調(diào)函數(shù)中是否包含 requet 請求,也就是第一個參數(shù) req施敢。如只驗證username周荐,和 password 可以關(guān)閉狭莱。
  }, (req, username, password, done) => {
    // 編輯 user 格式
    const user = {
      provider: 'local', // 策略的名字
      username,
      password,
    };
    app.logger.debug('%s %s get user: %j', req.method, req.url, user);
    app.passport.doVerify(req, user, done); // 觸發(fā)我們添加的驗證規(guī)則
  }));

  1. 可以看出 核心流程 就是 添加verify->觸發(fā)verifiy 就那么簡單僵娃。

API

egg-passport 提供了以下擴展:

  • ctx.user - 獲取當前已登錄的用戶信息
  • ctx.isAuthenticated() - 檢查該請求是否已授權(quán)
  • ctx.login(user, [options]) - 為用戶啟動一個登錄的 session
  • ctx.logout() - 退出,將用戶信息從 session 中清除
  • ctx.session.returnTo= - 在跳轉(zhuǎn)驗證前設(shè)置腋妙,可以指定成功后的 redirect 地址

還提供了 API:

  • app.passport.verify(async (ctx, user) => {}) - 校驗用戶
  • app.passport.serializeUser(async (ctx, user) => {}) - 序列化用戶信息后存儲進 session
  • app.passport.deserializeUser(async (ctx, user) => {}) - 反序列化后取出用戶信息
  • app.passport.authenticate(strategy, options) - 生成指定的鑒權(quán)中間件
    • options.successRedirect - 指定鑒權(quán)成功后的 redirect 地址
    • options.loginURL - 跳轉(zhuǎn)登錄地址默怨,默認為 /passport/${strategy}
    • options.callbackURL - 授權(quán)后回調(diào)地址,默認為 /passport/${strategy}/callback
  • app.passport.mount(strategy, options) - 語法糖骤素,方便開發(fā)者配置路由

注意:

  • app.passport.authenticate 中匙睹,未設(shè)置 options.successRedirect 或者 options.successReturnToOrRedirect 將默認跳轉(zhuǎn) /

egg-passport 序列化和反序列化

egg-passport 重寫了 passport 的 序列化方法 流程和 verify 差不多,。通過 cookie EGG_SESS 存著用戶的信息济竹。每次請求會驗證用戶 seesion id 對應(yīng)用戶信息痕檬。

通過添加 serializeUser()添加 序列化函數(shù),然后 框架會自動 在驗證完用戶的時候 存session 的時候執(zhí)行送浊。
通過deserializeUser()添加反序列化函數(shù)梦谜,在通過 session 獲取用戶的時候。
serializeUser 和 deserializeUser 必須有。否則無法保存用戶信息唁桩。

// 必須返回 user 哦。 兩個都要返回 报辱。不然 獲取不到用戶
  app.passport.serializeUser(async (ctx, user) => {

    return user
  });
  app.passport.deserializeUser(async (ctx, user) => {
    return user

  });
//源碼:
'use strict';

constdebug=require('debug')('egg-passport:passport');

constPassport=require('passport').Passport;

constSessionStrategy=require('passport').strategies.SessionStrategy;

constframework=require('./framework');

consturl=require('url');

classEggPassportextendsPassport{

constructor(app){

super();

this.app=app;

this._verifyHooks=[];

this._serializeUserHooks=[];

this._deserializeUserHooks=[];

}

/**

  * Overide the initialize authenticator to make sure `__monkeypatchNode` run once.

  */

init(){

this.framework(framework);

this.use(newSessionStrategy());

}

/**

  * Middleware that will authorize a third-party account using the given

  * `strategy` name, with optional `options`.

  *

  * Examples:

  *

  *    passport.authorize('twitter', { failureRedirect: '/account' });

  *

*@param{String} strategy - strategy provider name

*@param{Object} [options] - optional params

*@return{Function} middleware

*@apipublic

  */

authenticate(strategy,options={}){

// try to use successReturnToOrRedirect first

if(!options.hasOwnProperty('successRedirect')&&!options.hasOwnProperty('successReturnToOrRedirect')){

// app use set `ctx.session.returnTo = ctx.path` before auth redirect

options.successReturnToOrRedirect='/';

}

if(!options.hasOwnProperty('failWithError')){

options.failWithError=true;

}

returnsuper.authenticate(strategy,options);

}

session(){

returnthis._framework.session();

}

mount(strategy,options={}){

options.loginURL=options.loginURL||`/passport/${strategy}`;

options.callbackURL=options.callbackURL||`/passport/${strategy}/callback`;

constauth=this.authenticate(strategy,options);

this.app.get(url.parse(options.loginURL).pathname,auth);

this.app.get(url.parse(options.callbackURL).pathname,auth);

}

doVerify(req,user,done){

consthooks=this._verifyHooks;

if(hooks.length===0)returndone(null,user);

(async()=>{

constctx=req.ctx;

for(consthandlerofhooks){

user=awaithandler(ctx,user);

if(!user){

break;

}

}

done(null,user);

})().catch(done);

}

/**

  * Verify authenticated user

  *

*@param{Function} handler - verify handler

  */

verify(handler){

this._verifyHooks.push(this.app.toAsyncFunction(handler));

}

serializeUser(handler){

if(typeofhandler==='function'){

// serializeUser(async function (ctx, user))

this._serializeUserHooks.push(this.app.toAsyncFunction(handler));

}elseif(arguments.length===3){

// passport => http/request.js call passport.serializeUser(verifiedUser, req, done)

constverifiedUser=arguments[0];

constreq=arguments[1];

constdone=arguments[2];

returnthis._handleSerializeUser(req.ctx,verifiedUser,done);

}else{

debug(arguments);

thrownewError('Unkown serializeUser called');

}

}

deserializeUser(handler){

if(typeofhandler==='function'){

// deserializeUser(async function (ctx, user))

this._deserializeUserHooks.push(this.app.toAsyncFunction(handler));

}else{

// return promise

constctx=arguments[0];

constsessionUser=arguments[1];

returnthis._handleDeserializeUser(ctx,sessionUser);

}

}

_handleSerializeUser(ctx,verifiedUser,done){

consthooks=this._serializeUserHooks;

debug('serializeUserHooks length: %d',hooks.length);

// make sure profile proerty cleanup

if(verifiedUser&&verifiedUser.profile){

verifiedUser.profile=undefined;

}

if(hooks.length===0)returndone(null,verifiedUser);

(async()=>{

letsessionUser=verifiedUser;

for(consthandlerofhooks){

sessionUser=awaithandler(ctx,sessionUser);

if(!sessionUser){

break;

}

}

debug('serializeUser %j => %j',verifiedUser,sessionUser);

done(null,sessionUser);

})().catch(done);

}

async_handleDeserializeUser(ctx,sessionUser){

consthooks=this._deserializeUserHooks;

debug('deserializeUserHooks length: %d',hooks.length);

if(hooks.length===0)returnsessionUser;

letuser=sessionUser;

for(consthandlerofhooks){

user=awaithandler(ctx,user);

if(!user){

break;

}

}

debug('deserializeUser %j => %j',sessionUser,user);

returnuser;

}

}

module.exports=EggPassport;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市饥侵,隨后出現(xiàn)的幾起案子鸵赫,更是在濱河造成了極大的恐慌,老刑警劉巖辩棒,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件一睁,死亡現(xiàn)場離奇詭異者吁,居然都是意外死亡饲帅,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赦邻,“玉大人惶洲,你說我怎么就攤上這事∏┰颍” “怎么了渐裂?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵侨颈,是天一觀的道長哈垢。 經(jīng)常有香客問我扛拨,道長绑警,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮北启,結(jié)果婚禮上咕村,老公的妹妹穿的比我還像新娘懈涛。我一直安慰自己,他們只是感情好宇植,可當我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布指郁。 她就那樣靜靜地躺著坡氯,像睡著了一般洋腮。 火紅的嫁衣襯著肌膚如雪啥供。 梳的紋絲不亂的頭發(fā)上库糠,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天贷屎,我揣著相機與錄音,去河邊找鬼咒吐。 笑死属划,一個胖子當著我的面吹牛同眯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播硅确,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼晤愧!你這毒婦竟也來了官份?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎赋元,沒想到半個月后飒房,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體狠毯,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡嚼松,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年寝受,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片漓帅。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡忙干,死狀恐怖捐迫,靈堂內(nèi)的尸體忽然破棺而出爱葵,到底是詐尸還是另有隱情萌丈,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站度迂,受9級特大地震影響惭墓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜划咐,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望划煮。 院中可真熱鬧弛秋,春花似錦、人聲如沸登失。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽草姻。三九已至,卻和暖如春敞曹,著一層夾襖步出監(jiān)牢的瞬間澳迫,已是汗流浹背剧劝。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工讥此, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留暂论,地道東北人。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像闻蛀,于是被迫代替她去往敵國和親觉痛。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,925評論 2 344