以PassportJS + MongoDB建立一個具有認證機制的網頁應用程式
認證是許多應用程式會有的功能,本文就來介紹NodeJS中最被廣泛使用的認證函式庫-PassportJS。PassportJS基本上是用來作為expressJS中的middleware,負責處理認證的部分,passportJS是一個強大且完整的函式庫,提供多種不同的認證方式讓你依照自己的需求來選擇(稱作strategy)。所謂完整即是說大部份的認證方式(FB, Google, OAuth, OpenID)都已經有寫好的strategy,僅需要把對應的strategy lib抓下來即可使用,而如果有需要自定模組的話也可以自己寫一個strategy來處理。
由於passportJS的doc實在寫的不是很完整,因此我寫了一個範例的project跟這篇文章來解釋一下整個流程,以passport-local的strategy也就是使用自己local的資料庫來做認證,以下先介紹project的大致架構,再來介紹passport的用法。
功能簡介:
- 連至此web app的頁面會先被導向登入頁面
- 認證之後依照權限(管理員與非管理員)顯示不同功能
- 非管理員僅只能看到index頁面,及登出功能
- 管理員除了index頁面之外另外可以看到此系統的使用者列表
使用情境:
- 使用者連至網頁
- 系統檢查該網頁是否需要授權
- 如果需要授權且使用者沒登入,則導向登入頁面
- 顯示頁面。在這我們把環境簡化一些,使用者的資料是儲存在自己的MongoDB裡;而使用者的權限也僅簡單的分為兩種:一般使用者(user)以及管理者(admin)。
Github Repository
此範例程式修改自passport-local裡的範例範例程式Github
(註: 此project以coffeescript撰寫,若對coffeescript不了解可以用js2coffee轉換為javascript)
檔案目錄結構
example-passport/
config/ //project設定黨,以json格式儲存
public/ //web端需要的圖片, js, css
lib/ //coffeescript 產生之js檔
src/ //coffeescript 原始碼
test/ //unit test
web/ //web app主程式碼
controllers/ //各頁面的controller
models/ //models (mongoose)
util/ //其他
app.coffee //程式進入點
views/ //頁面模板
Cakefile //coffeescript版本的makefile
package.json //npm config file
檔案目錄很簡單,以MVC為架構區分controller, models跟views,其中controllers及models是coffeescript file,故放在src底下。而views是以jade撰寫,並不需要額外compile,故放在src之外。其他各檔案基本參照nodejs慣例用法存放。接下來看看要如何加入認證的功能。
網頁路徑設定
針對要認證的頁面加入認證的middlewareapp.coffee 65行之後是web path routing設定。程式碼如下:
#WEB Route
app.get('/', pass.ensureAuthenticated, views.index)
app.get('/login', user.getlogin)
app.post('/login', user.postlogin)
app.get('/logout', user.logout)
app.get('/admin/users', pass.ensureAuthenticated, pass.ensureAdmin(), user.listUser)
#app.post('/admin/users', user_routes.createUser)
app.get('/admin/users/:id', pass.ensureAuthenticated, pass.ensureAdmin(), user.editUser)
可以看到這裡對/
,/admin/users
以及/admin/users/:id
三個路徑安插了認證的middleware。注意/admin/users
以及/admin/users/:id
這兩個頁面屬於管理員(admin)層級才看得到的,故多了一個pass.ensureAdmin()的middleware來確保是管理員身份,相較之下/
的路徑就只需要確保使用者有登入即可。
兩個middleware程式碼其實都很簡單,在src/web/util/pass.coffee 看得到。
另外如果認證是透過session來存取時,要記得加上express的session支援(app.coffee的 58 & 60行)
如何認證
首先,我們需要先定義Strategy (放在src/web/util/pass.coffee )passport.use new LocalStrategy((username, password, done) ->
User.findOne
username: username
, (err, user) ->
return done(err) if err
unless user
return done(null, false,
message: "Invalid username or password"
)
user.comparePassword password, (err, isMatch) ->
return done(err) if err
if isMatch
done null, user
else
done null, false,
message: "Invalid username or password"
)
Strategy簡單的說,就是定義一個認證的流程,在這project中由於我們是採用local資料庫儲存使用者資料,故可以看到程式碼中的user.comparePassword
其實是從資料庫中取得使用者資料來比對是否為合法物件。
要定義一個LocalStrategy,語法是這樣:(src/web/util/pass.coffee )
passport.use(new LocalStrategy((username, password, done) ->
#在此確認username/password是否正確
#如果正確 則回傳 done(null, user), user為使用者物件
#如果賬號密碼錯誤,則回傳 done(null, false, {message:"失敗訊息"})
#如果有其他錯誤則回傳 done(err),err是錯誤物件
)
Strategy定義好了之後還需要使用,而會使用到的地方當然就是登入頁面 src/web/controllers/users.coffee
# POST /login
# This is an alternative implementation that uses a custom callback to
# acheive the same functionality.
exports.postlogin = (req, res, next) ->
passport.authenticate("local", (err, user, info) ->
return next(err) if err
unless user
req.session.messages = [info.message]
return res.redirect("/login")
req.logIn user, (err) ->
return next(err) if err
res.redirect "/"
) req, res, next
passport.authenticate的第一個參數是用來選擇strategy,local代表的是使用LocalStrategy,也就是我們之前定義的。第二個參數對應的就是之前定義的done,我們可以看到三個參數err, user, info有互相對應。在這裡要注意的是req.logIn這個function,他會把第一個參數(user)設定為req.user,這代表了req.isAuthenticated會回傳true,這樣就可以通過認證了。
至此我們已經有一個具有認證功能的web app了,之後會再介紹如何不使用session的情況下完成使用者認證。(適用於REST API)
沒有留言:
張貼留言