1. 綜述 GENERAL
Simple, unobtrusive authentication for Node.js
- 1.綜述 GENERAL
- 1.1. 概覽 Overview
- 1.2. 認證 Authenticate
- 1.2.1. 重定向 Redirects
- 1.2.2. 快報 Flash Messages
- 1.2.3. 禁用會話 Disable Sessions
- 1.2.4. 自定義回調函數 Custom Callback
- 1.3. 配置 Configure
- 1.3.1. 政策 Strategies
- 1.3.2. 驗證回調 Verify Callback
- 1.3.3. 中間件 Middleware
- 1.3.4. 會話 Session
- 1.4. 使用者名和密碼 Username & Password
- 1.4.1. 配置 Configuration
- 1.4.2. 表格 Form
- 1.4.3. 路由 Route
- 1.4.4. 參數 Parameters
- 1.5. OpenID
- 1.6. OAuth
- 2.内置操作 Operations
- 2.1. 登入 Log In
- 2.2. 登出 Log Out
- 2.3. 授權 Authorize
- 3.具體流程
- 3.1. 第一次通路 (GET)
- 3.2. 第二次通路 (POST)
- 3.3. 第三次通路 (GET)
- 3.4. 使用者登出 (GET)
1.1. 概覽 Overview
Passport 是 Node 的認證中間件,它的存在隻有一個單一的目的,就是認證請求。
在現今的網絡應用中認證方式多種多樣。經典做法是使用者提供使用者名和密碼進行登入,但随着社交網絡的興起,OAuth 等基于密碼的方式越來越受到歡迎。
在 Passport 設計理念中就認為每一個網絡應用擁有不同的認證需求。是以為了滿足各種網絡應用,被稱作政策的認證機制被封裝成單一的子產品中,網絡應用可自由選擇不同政策來實作認證功能。
認證機制本身可能比較複雜,但是在 Passport 中編寫的代碼并沒有那麼繁瑣:
app.post('/login',
passport.authenticate('local',
{ successRedirect: '/',
failureRedirect: '/login'
} ));
當然首先需要安裝 Passport:
$ npm install passport
1.2. 認證 Authenticate
通過調用
passport.authenticate()
方法及配置相應的政策,就可實作認證網絡請求。
authenticate()
方法是标準的Node 中間件,在 Express 應用中可以非常友善的作為路由使用。
app.post('/login',
passport.authenticate('local'),
function(req, res) {
// 如果認證通過,将觸發該函數
// `req.user` 字段内有認證的使用者名.
res.redirect('/users/' + req.user.username);
});
在預設情況下,認證失敗後 Passport 會傳回
401 Unauthorized
狀态的響應,其後面的處理函數也不會被觸發。當然認證成功後,處理函數被觸發并将認證使用者資訊作為值指派給
req.user
。
注意: 政策必須在路由使用它之前進行配置。
1.2.1. 重定向 Redirects
在認證請求後通常接下來需要處理的事務就是重定向。
app.post('/login',
passport.authenticate('local',
{ successRedirect: '/',
failureRedirect: '/login'
})
);
上述代碼中,如果認證成功将會被重定向到首頁,如果失敗會重新來到登入頁面。
1.2.2. 快報 Flash Messages
當認證失敗後用戶端被重定向到登入頁面,重定向後的頁面中經常會顯示一些狀态提示資訊,如:登入失敗。那麼如何分辨登入頁面是認證失敗後的重定向還是第一次通路?差別它們的方法就是在 session 中寫入一個特殊的辨別
flash
。
app.post('/login',
passport.authenticate('local',
{ successRedirect: '/',
failureRedirect: '/login',
failureFlash: true
})
);
上述代碼會在認證失敗後,将政策的認證函數中傳回的資訊寫入 flash。這是最常用和最好的方法,因為每個政策的認證函數都會把認證失敗的準确資訊傳回。
當然也可以自定義快報:
passport.authenticate('local',
{failureFlash: 'Invalid username or password.' }
);
或者是認證成功後的快報:
passport.authenticate('local',
{ successFlash: 'Welcome!' }
);
注意: 在 Express 4.x 中使用快報需要添加 connect-flash
中間件。
1.2.3. 禁用會話 Disable Sessions
由于 HTTP 是無狀态協定,是以在認證成功後通常是将登入資訊儲存在 session 中。但是在某些情況下并不需要 session,如 API 服務需要證書來認證,此時可以放心的禁用會話。
app.get('/api/users/me',
passport.authenticate('basic',
{ session: false }),
function(req, res) {
res.json({ id: req.user.id,
username: req.user.username
});
});
1.2.4. 自定義回調函數 Custom Callback
在處理認證請求時,可自定義回調函數來處理成功或失敗的認證。
app.get('/login', function(req, res, next) {
passport.authenticate('local',
function(err, user, info) {
if (err) { return next(err); }
if (!user) { return res.redirect('/login'); }
req.logIn(user, function(err) {
if (err) { return next(err); }
return res.redirect('/users/' + user.username);
});
})(req, res, next);
});
上例中,方法
authenticate()
沒有作為路由的中間件出現,而是在路由的處理函數中被調用。該方法的回調函數的參數通過閉包從路由中傳入。如果認證失敗
user
将被指派為
false
。出現異常
err
會被初始化并傳回異常資訊。可選的
info
則傳回政策中相關資訊。
注意: 使用自定義回調函數,應用必須建立一個 session (上例通過 req.logIn()) 并發送響應。
1.3. 配置 Configure
在認證過程中 Passport 需要配置三個部分:
- 認證政策
- 應用中間件
- 會話(可選)
1.3.1. 政策 Strategies
Passport 通過政策來認證網絡請求。政策可以是使用者名和密碼的确認認證,可以是 OAuth 的委派認證,還可以是 OpenID 的聯合認證。正如上文所提到的政策在使用前必須進行配置。
政策及其配置通過
use()
方法實作。比如接下來的通過使用者名和密碼的認證政策
LocalStrategy
:
var passport = require('passport')
var LocalStrategy = require('passport-local').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: 'Incorrect username.' });
}
if (!user.validPassword(password)) {
return done(null, false,
{ message: 'Incorrect password.' });
}
return done(null, user);
});
}
));
1.3.2. 驗證回調 Verify Callback
上面的例子中有個很重要的概念:政策中需要驗證回調。驗證回調目的是找到擁有認證資訊的使用者。
當 Passport 驗證一個請求時,會解析請求中的認證資訊,然後将認證資訊發送給驗證回調函數同時觸發該函數。如果認證資訊有效,驗證回調函數觸發
done
函數,将認證的使用者資訊傳回給 Passport 。
如果認證資訊無效,
done
函數同樣也會被觸發,但是傳回的是
false
。
一些附加資訊可以追加在
done
函數的第三個參數中,這些資訊可用來呈現快報。
最後如果在驗證過程中出現異常,
done
函數可以傳遞一個 Node 風格的 err 資訊。
注意: 區分開兩種驗證失敗的原因,如果是伺服器的異常函數的第一個參數
done
設定為非空值;驗證條件的失敗要確定
err
為
err
。
null
這種委派方式確定了 Passport 資料庫對驗證回調函數的透明,應用可任意選擇使用者資訊的存儲方式。
1.3.3. 中間件 Middleware
在 Express 應用中,
passport.initialize()
中間件可初始化 Passport,
passport.session()
中間件用來存儲使用者登入的 session 資訊。
var app = express();
app.use(require('serve-static')(__dirname + '/../../public'));
app.use(require('body-parser').urlencoded({ extended: true }));
app.use(require('express-session')({ secret: 'keyboard cat', resave: true, saveUninitialized: true }));
app.use(passport.initialize());
app.use(passport.session());
注意:中間件應該在
express.session()
中間件前面。
passport.session()
1.3.4. 會話 Session
在典型的網絡應用中,登入請求中包含驗證使用者的認證資訊。如果認證成功,使用者浏覽器中通過 cookie 建立并儲存 sessionID。随後所有的請求不再需要驗證,而是通過 sessionID 來識别使用者。Passport 可以将 session 中的使用者資訊序列化或反序列化,以此支援 session 機制。
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
上述代碼中,user ID 被序列化到 session 中,接下來的請求中,通過 user ID 擷取使用者資訊并将其存入到
req.user
中。
序列化和反序列化的邏輯由應用提供,可以選擇适合的資料庫存儲會話,在認證層面這些操作沒有任何的限制。
1.4. 使用者名和密碼 Username & Password
網絡中最常用的方式就是通過使用者名和密碼進行認證,提供這種認證的政策是
passport-local
。
$ npm install passport-local
1.4.1. 配置 Configuration
var passport = require('passport')
, LocalStrategy = require('passport-local').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: 'Incorrect username.' });
}
if (!user.validPassword(password)) {
return done(null, false,
{ message: 'Incorrect password.' });
}
return done(null, user);
});
}
));
本地認證的驗證回調函數接受
username
和
password
兩個參數。
1.4.2. 表格 Form
表格提供了
username
和
password
這兩個參數:
<form action="/login" method="post">
<div>
<label>Username:</label>
<input type="text" name="username"/>
</div>
<div>
<label>Password:</label>
<input type="password" name="password"/>
</div>
<div>
<input type="submit" value="Log In"/>
</div>
</form>
1.4.3. 路由 Route
登入表格資訊通過
POST
方法送出給伺服器,
local
政策使用
authenticate()
函數處理登入請求:
app.post('/login',
passport.authenticate('local',
{ successRedirect: '/',
failureRedirect: '/login',
failureFlash: true
})
);
設定
failureFlash
選項為
true
,意味着在驗證回調函數中傳回的
message
資訊将成為錯誤快報的值。
1.4.4. 參數 Parameters
預設情況下,
localStrategy
政策使用
username
和
password
作為認證機制的參數,實際上其他字段也可以作為參數進行驗證:
passport.use(new LocalStrategy({
usernameField: 'email',
passwordField: 'passwd'
},
function(username, password, done) {
// ...
}
));
1.5. OpenID
暫時用不到 _
1.6. OAuth
暫時用不到 _
2. 内置操作 Operations
2.1. 登入 Log In
Passport 通過暴露給
req
的
login()
方法建立登入會話。
req.login(user, function(err) {
if (err) { return next(err); }
return res.redirect('/users/' + req.user.username);
});
登入操作完成後,使用者資訊被指派在
req.user
上。
注意:中間件會自動觸發
passport.authenticate()
。 這項功能主要使用在使用者注冊時,調用
req.login()
方法自動登入新注冊的使用者。
req.login()
2.2. 登出 Log Out
與
login()
相反,
logout()
方法用來結束登入會話。 調用
logout()
方法會删除
req.user
屬性并清除登入會話。
app.get('/logout', function(req, res){
req.logout();
res.redirect('/');
});
2.3. 授權 Authorize
暫時用不到 _
3. 具體流程
3.1. 第一次通路 (GET)
- 用戶端通路伺服器登入頁面。
- 伺服器生成sessionid。 (express-session)
- 伺服器将seesionid對應的session儲存在資料庫中。 (express-session)
- 在session中添加crsf。 (cursf)
- 伺服器将sessionid儲存到cookie中,傳回給用戶端,同時将csrf token傳回給用戶端。 (express-session)
- 用戶端儲存cookie。
- 用戶端填寫登入内容。
3.2. 第二次通路 (POST)
- 用戶端将登陸内容、cookie和csrf token發送給伺服器。
- 伺服器查找對應sessionid的session。 (express-session)
- 伺服器驗證csrf token。 (cursf)
- 伺服器驗證登入内容。 (passport/autenticate)
- 伺服器将使用者資訊挂載在 req.user。 (passport/req.login)
- 将使用者資訊序列化成userid儲存到session中。 (passport/serialize)
- 伺服器重定向。
3.3. 第三次通路 (GET)
- 用戶端通路頁面。
- 用戶端發送cookie和csrf token。
- 伺服器查找與sessionid對應session。 (express-session)
- 伺服器驗證csrf token。(GET 方法忽略驗證)
- 伺服器将session中的userid反序列化,附加到 req.user。 (passport/deserialize)
- 伺服器傳回使用者内容。
3.4. 使用者登出 (GET)
- 用戶端發送登出資訊
- 用戶端發送出去cookie和csrf token。
- 伺服器查找sessionid對應的session。 (express-session)
- 伺服器調用req.logout将userid從session删除。 (passport/req.logout)
- 伺服器重定向。
原文連結:https://www.jianshu.com/p/2a3c178e2e9c