寫教程的環(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ù)化存取用戶信息麦萤。
代碼順序
在 plugin.js 添加 egg-passport 插件,并打開扁眯。
在 router.js 添加 需要驗證的地址
router.post('/login', app.passport.authenticate('local', { successRedirect: '/authCallback' }));新建 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
});
- 實例化我們的策略
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ī)則
}));
- 可以看出 核心流程 就是 添加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;