雖然基本用法已經(jīng)能覆蓋一般需求了,但是還是有各種edge case的存在。passport也提供了一些擴(kuò)展性的功能來應(yīng)對不同的場景。下面就來講一下。
有的時候,在登錄時不但需要驗(yàn)證用戶名和密碼,還需要驗(yàn)證一個附加條件,比如Discuz!論壇程序提供的功能:
這種情況passport也是支持的。
在配置策略的時候,Strategy接受一個options參數(shù),它包含一個passReqToCallback項(xiàng),默認(rèn)為false,設(shè)置為true時可以將整個req傳遞給回調(diào)函數(shù),這樣在回調(diào)里就可以驗(yàn)證req中帶的所有條件了。示例代碼如下:
passport.use(new LocalStrategy(
{passReqToCallback: true},
function(req, username, password, done) {
// now you can check any req.body.xxx
}
));
這個方法在OAuth驗(yàn)證中也是支持的。
最常見的case是貼吧,我們支持已登錄的用戶發(fā)帖,但是對匿名用戶也支持發(fā)帖回帖,但對它們屏蔽了一些高級用法。
這里需要用到匿名驗(yàn)證策略passport-anonymous,它的使用非常簡單,只需要聲明一句就行了:
passport.use(new AnonymousStrategy());
要實(shí)現(xiàn)多種驗(yàn)證策略,可如下配置:
var passport = require('passport')
, LocalStrategy = require('passport-local').Strategy
, AnonymousStrategy = require('passport-anonymous').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, { message: '用戶名不存在.' });
}
if (!user.validPassword(password)) {
return done(null, false, { message: '密碼不匹配.' });
}
return done(null, user);
});
}
));
//匿名登錄認(rèn)證作為本地認(rèn)證的fallback
passport.use(new AnonymousStrategy());
...
app.get('/',
passport.authenticate(['local', 'anonymous'], { session: false }),
function(req, res){
if (req.user) {
res.json({ msg: "用戶已登錄"});
} else {
res.json({ msg: "用戶以匿名方式登錄"});
}
});
我們可以看到使用多種驗(yàn)證策略時,可使用數(shù)組來將策略名稱作為參數(shù)傳給authenticate方法。
匿名驗(yàn)證是作為local驗(yàn)證的fallback而存在,當(dāng)local驗(yàn)證不滿足時會調(diào)用后面的驗(yàn)證方法。
理論上講這種調(diào)用方法也可用于其它驗(yàn)證,但不鼓勵這么做,它會讓用戶系統(tǒng)變得極為復(fù)雜難以維護(hù)。、
最后注意:使用匿名驗(yàn)證時需要將session設(shè)為false,不將匿名用戶信息存儲到session中。
還有一種使用場景是將所有的匿名用戶都視為游客,這樣你可以通過用戶管理頁面來操作所有的游客的信息。
你可以通過passport-anonymous來實(shí)現(xiàn)這一點(diǎn),將所有的匿名用戶都賦予同一個用戶ID,但還有一個passport插件來專門做這件事,那就是passport-dummy,它的使用方法如下:
var passport = require('passport')
, DummyStrategy = require('passport-dummy').Strategy;
// 設(shè)置部分
passport.use(new DummyStrategy(
function(done) {
return done(null, {username: 'dummy'});
}
));
// 路由部分
app.post('/login',
passport.authenticate('dummy', { failureRedirect: '/login' }),
function(req, res) {
res.redirect('/');
});
實(shí)際使用中經(jīng)常需要與其他驗(yàn)證方法結(jié)合起來,比如在上面的多種驗(yàn)證策略中代替匿名登錄驗(yàn)證。
這里要明白,passport只是做登錄驗(yàn)證的,只是操作session,甚至連驗(yàn)證都是用戶自己完成,它本身是個很獨(dú)立的模塊,但是由于其使用了connect中的一些方法,因此與connect有些耦合。
因此一般的connect-style 的web框架passport都能直接使用,無需適配。
那么要在其它web框架中如Hapi中能使用passport嗎?
回答是理論上能,但不建議直接這么做。一般這樣的框架也應(yīng)該都有自己的驗(yàn)證模塊或插件,如果實(shí)在不能滿足需求,非要用passport,這里提供一下思路:首先要支持session,然后構(gòu)造類似express中的req和res,起碼要支持res.session各種操作,以及將http request賦值為req。
這里有Hapi框架使用passport的教程,可以看到非常麻煩, 它本身已經(jīng)有hapi-auth-cookie和bell模塊用于驗(yàn)證,所以不必執(zhí)著于使用passport。
RESTful API的驗(yàn)證分為兩種情況。一種是面向自己app的用戶,其驗(yàn)證和http驗(yàn)證一樣。另一種是向第三方提供的API,這種情況下可能每次連接都需要驗(yàn)證,反而不需要用到session了。passport也支持這樣的做法。
想要每次連接都進(jìn)行驗(yàn)證,只需將authenticate方法作為中間件添加到需要驗(yàn)證的URL里,然后將session設(shè)為false:
app.get('/api/users/me',
passport.authenticate('basic', { session: false }),
function(req, res) {
res.json({ id: req.user.id, username: req.user.username });
});
這樣每次連接都需要經(jīng)過authenticate,并且不會被記錄在session里。
passport以插件的形式支持了很多第三方網(wǎng)站和服務(wù)的OAuth驗(yàn)證,但并不是所有的,如果你需要在app中用到第三方的服務(wù),但它們沒有對應(yīng)的passport插件,你可以用通用的OAuth或其他驗(yàn)證方法來進(jìn)行驗(yàn)證,也可以將它們封裝成passport-x插件。
制作passport插件并不困難,因?yàn)樗皇菍Σ呗缘呐渲貌糠诌M(jìn)行了一些封裝而已,你可以將一個已有的passport插件稍微修改即可。
比如要制作一個OAuth2.0的passport插件,你可以以passport-github為模板,甚至只需要修改它的strategy.js,步驟總結(jié)如下:
沒錯,就是這么簡單!
如果僅僅為自己使用,上面的步驟已經(jīng)足夠,但你還可以將它們分享出來給別人使用,這需要額外的步驟:
現(xiàn)在你也是passport的貢獻(xiàn)者了!~
上面我們只是講到了local驗(yàn)證和OAuth驗(yàn)證,以及提到了一下anonymous驗(yàn)證,但實(shí)際上passport支持的驗(yàn)證方法不止這幾種,具體支持的驗(yàn)證方案可以參見這個頁面。
在多數(shù)時候,我們只需要引入這些實(shí)現(xiàn)方案,就可以進(jìn)行驗(yàn)證。
這里的驗(yàn)證方案在我們想向第三方app提供RESTful API時非常有用。
一些學(xué)習(xí)passport具體實(shí)現(xiàn)的Nodejs項(xiàng)目。
更多建議: