Egg Passport

2020-02-06 14:11 更新

『登錄鑒權(quán)』 是一個(gè)常見的業(yè)務(wù)場(chǎng)景,包括『賬號(hào)密碼登錄方式』和『第三方統(tǒng)一登錄』。

其中,后者我們經(jīng)常使用到,如 Google, GitHub,QQ 統(tǒng)一登錄,它們都是基于 OAuth 規(guī)范。

Passport 是一個(gè)擴(kuò)展性很強(qiáng)的認(rèn)證中間件,支持 Github,Twitter,F(xiàn)acebook 等知名服務(wù)廠商的 Strategy,同時(shí)也支持通過賬號(hào)密碼的方式進(jìn)行登錄授權(quán)校驗(yàn)。

Egg 在它之上提供了 egg-passport 插件,把初始化、鑒權(quán)成功后的回調(diào)處理等通用邏輯封裝掉,使得開發(fā)者僅需調(diào)用幾個(gè) API 即可方便的使用 Passport 。

Passport 的執(zhí)行時(shí)序如下:

  • 用戶訪問頁(yè)面
  • 檢查 Session
  • 攔截跳鑒權(quán)登錄頁(yè)面
  • Strategy 鑒權(quán)
  • 校驗(yàn)和存儲(chǔ)用戶信息
  • 序列化用戶信息到 Session
  • 跳轉(zhuǎn)到指定頁(yè)面

使用 egg-passport

下面,我們將以 GitHub 登錄為例,來(lái)演示下如何使用。

安裝

$ npm i --save egg-passport
$ npm i --save egg-passport-github

更多插件參見 GitHub Topic - egg-passport 。

配置

開啟插件:

// config/plugin.js
module.exports.passport = {
enable: true,
package: 'egg-passport',
};

module.exports.passportGithub = {
enable: true,
package: 'egg-passport-github',
};

配置:

注意:egg-passport 標(biāo)準(zhǔn)化了配置字段,統(tǒng)一為 key 和 secret 。

// config/default.js
config.passportGithub = {
key: 'your_clientID',
secret: 'your_clientSecret',
// callbackURL: '/passport/github/callback',
// proxy: false,
};

注意:

  • 創(chuàng)建一個(gè) GitHub OAuth Apps,得到 clientID 和 clientSecret 信息。
  • 填寫 callbackURL,如 http://127.0.0.1:7001/passport/github/callback線上部署時(shí)需要更新為對(duì)應(yīng)的域名路徑為配置的 options.callbackURL,默認(rèn)為 /passport/${strategy}/callback
  • 如應(yīng)用部署在 Nginx/HAProxy 之后,需設(shè)置插件 proxy 選項(xiàng)為 true, 并檢查以下配置:代理附加 HTTP 頭字段:x-forwarded-proto 與 x-forwarded-host配置中 config.proxy 應(yīng)設(shè)置為 true

掛載路由

// app/router.js
module.exports = app => {
const { router, controller } = app;

// 掛載鑒權(quán)路由
app.passport.mount('github');

// 上面的 mount 是語(yǔ)法糖,等價(jià)于
// const github = app.passport.authenticate('github', {});
// router.get('/passport/github', github);
// router.get('/passport/github/callback', github);
}

用戶信息處理

接著,我們還需要:

  • 首次登錄時(shí),一般需要把用戶信息進(jìn)行入庫(kù),并記錄 Session 。
  • 二次登錄時(shí),從 OAuth 或 Session 拿到的用戶信息,讀取數(shù)據(jù)庫(kù)拿到完整的用戶信息。
// app.js
module.exports = app => {
app.passport.verify(async (ctx, user) => {
// 檢查用戶
assert(user.provider, 'user.provider should exists');
assert(user.id, 'user.id should exists');

// 從數(shù)據(jù)庫(kù)中查找用戶信息
//
// Authorization Table
// column | desc
// --- | --
// provider | provider name, like github, twitter, facebook, weibo and so on
// uid | provider unique id
// user_id | current application user id
const auth = await ctx.model.Authorization.findOne({
uid: user.id,
provider: user.provider,
});
const existsUser = await ctx.model.User.findOne({ id: auth.user_id });
if (existsUser) {
return existsUser;
}
// 調(diào)用 service 注冊(cè)新用戶
const newUser = await ctx.service.user.register(user);
return newUser;
});

// 將用戶信息序列化后存進(jìn) session 里面,一般需要精簡(jiǎn),只保存?zhèn)€別字段
app.passport.serializeUser(async (ctx, user) => {
// 處理 user
// ...
// return user;
});

// 反序列化后把用戶信息從 session 中取出來(lái),反查數(shù)據(jù)庫(kù)拿到完整信息
app.passport.deserializeUser(async (ctx, user) => {
// 處理 user
// ...
// return user;
});
};

至此,我們就完成了所有的配置,完整的示例可以參見:eggjs/examples/passport

API

egg-passport 提供了以下擴(kuò)展:

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

還提供了 API:

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

注意:

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

使用 Passport 生態(tài)

Passport 的中間件很多,不可能都進(jìn)行二次封裝。 接下來(lái),我們來(lái)看看如何在框架中直接使用 Passport 中間件。 以『賬號(hào)密碼登錄方式』的 passport-local 為例:

安裝

$ npm i --save passport-local

配置

// app.js
const LocalStrategy = require('passport-local').Strategy;

module.exports = app => {
// 掛載 strategy
app.passport.use(new LocalStrategy({
passReqToCallback: true,
}, (req, username, password, done) => {
// format user
const user = {
provider: 'local',
username,
password,
};
debug('%s %s get user: %j', req.method, req.url, user);
app.passport.doVerify(req, user, done);
}));

// 處理用戶信息
app.passport.verify(async (ctx, user) => {});
app.passport.serializeUser(async (ctx, user) => {});
app.passport.deserializeUser(async (ctx, user) => {});
};

掛載路由

// app/router.js
module.exports = app => {
const { router, controller } = app;
router.get('/', controller.home.index);

// 鑒權(quán)成功后的回調(diào)頁(yè)面
router.get('/authCallback', controller.home.authCallback);

// 渲染登錄頁(yè)面,用戶輸入賬號(hào)密碼
router.get('/login', controller.home.login);
// 登錄校驗(yàn)
router.post('/login', app.passport.authenticate('local', { successRedirect: '/authCallback' }));
};

如何開發(fā)一個(gè) egg-passport 插件

在上一節(jié)中,我們學(xué)會(huì)了如何在框架中使用 Passport 中間件,我們可以進(jìn)一步把它封裝成插件,回饋社區(qū)。

初始化:

$ npm init egg --type=plugin egg-passport-local

在 package.json 中配置依賴:

{
"name": "egg-passport-local",
"version": "1.0.0",
"eggPlugin": {
"name": "passportLocal",
"dependencies": [
"passport"
]
},
"dependencies": {
"passport-local": "^1.0.0"
}
}

配置:

// {plugin_root}/config/config.default.js
// https://github.com/jaredhanson/passport-local
exports.passportLocal = {
};

注意:egg-passport 標(biāo)準(zhǔn)化了配置字段,統(tǒng)一為 key 和 secret,故若對(duì)應(yīng)的 Passport 中間件屬性名不一致時(shí),開發(fā)者應(yīng)該進(jìn)行轉(zhuǎn)換。

注冊(cè) passport 中間件:

// {plugin_root}/app.js
const LocalStrategy = require('passport-local').Strategy;

module.exports = app => {
const config = app.config.passportLocal;
config.passReqToCallback = true;

app.passport.use(new LocalStrategy(config, (req, username, password, done) => {
// 把 Passport 插件返回的數(shù)據(jù)進(jìn)行清洗處理,返回 User 對(duì)象
const user = {
provider: 'local',
username,
password,
};
// 這里不處理應(yīng)用層邏輯,傳給 app.passport.verify 統(tǒng)一處理
app.passport.doVerify(req, user, done);
}));
};


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)