天天看點

Node 認證中間件 Passport 學習筆記1. 綜述 GENERAL2. 内置操作 Operations3. 具體流程

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

繼續閱讀