[Node.js] OAuth 2 和 passport框架


原文地址:http://www.moye.me/?p=592

OAuth是什么

OAuth(開放授權)是一個開放標准,允許用戶讓第三方應用訪問該用戶在某一網站上存儲的私密的資源(如照片,視頻,聯系人列表),而無需將用戶名和密碼提供給第三方應用。

OAuth 2.0  OAuth 2

OAuth的版本有v1.0, v1.0a 和 v2.0。OAuth 2.0 的出現主要是解決1.0+中的幾個問題,提升開發簡易度和應用安全性:

  • 更好的支持非瀏覽器APP(移動和桌面客戶端,可以省略v1.0的交換token過程,增強用戶體驗)
  • 給訪問令牌(Access-Token)添加了續期概念,對令牌泄露多了一層防御
  • 不再強制客戶端使用Token Secret,有令牌就夠了
  • 引入Bearer 驗證機制

OAuth 2 是目前被廣泛支持的版本,但不向下兼容1.0。

術語

簡單說,OAuth 2涉及到三方

  1. Client APP:要訪問用戶的程序
  2. Resource Owner :用戶,由他來給APP授權
  3. Authorization Server:也稱為 API Provider(Google/Facebook/Twitter…)
    1. client_id 和 client_secret:Server頒發給 APP 的身份憑證
    2. Access Token:Server頒發的令牌,訪問資源API需要帶着它
    3. Scope:APP 向Server 提供需要調用的API種類
    4. Redirect URL:APP 向 Server提供的回調地址

OAuth 2 交互模型

OAuth 2的 交互模型比較靈活,主要的交互模型分為如下幾種:

  • 服務端Authorization code授權:被 Web服務端編程廣泛采用,也是本文重點介紹的模型
  • 客戶端隱式授權:Web客戶端編程(JavaScript APP)使用這種方式交互,在用戶授權后直接取到令牌
  • Resource Owner登錄授權:需要用戶輸入用戶名和密碼來交換令牌
  • 客戶端憑證:這種模型下,APP 可能就是Owner,代表自己進行API調用

准備工作

首先,開發人員需要去API Provider處,為APP 創建應用信息,申請權限,以Google為例:

  1. 在 Google Developers Console 創建項目
  2. 在 APIs & Auth -> APIs 中,選擇打開需要調用的API
  3. 在 APIs & Auth -> Credentials 中,填寫 Redirect URIS(OAuth 2 回調的地址),創建Client ID。成功后,會得到需要的 CLIENT ID 和 CLIENT SECRET,保存好不要泄露 :)
  4. 在 APIs & Auth -> Consent screen 中,填寫 Email/ Product Name/ Homepage/ Logo等 APP元信息,這些是給用戶看的,在交互過程中會出現在確認授權頁

服務端交互流程

OAuth 2 的服務端交互流程,是一個對用戶透明的三方過程:

Server-side Web Application flow: Step-by-step

如果用Node.js 實現前述的 Google API OAuth 2訪問,編程模型大概如此:

  1. 判斷是否已有 access_token,過期了嗎?如果不存在或過期,一步步來:
  2. 將用戶頁面跳向到https://accounts.google.com/o/oauth2/auth(附上一系列Query參數:response_type/ client_id/ redirect_uri/ scope,視需要追加參數:access_type/ approval_prompt/ state…)
  3. 為 redirect_uri 提供 HTTP GET 方法的處理,以響應回調
  4. 第3步的回調會收到一個code參數,用它向 https://accounts.google.com/o/oauth2/token 發起一個 POST請求,這次需要提供的參數:code / client_id/ client_secret/ redirect_uri/ grant_type
  5. 為 第4步 的redirect_uri 提供HTTP GET 方法的處理,以響應回調
  6. 第5步的回調會收到一個 JSON對象,里面有access_token 和它的過期時間expires_in,將它存下來
  7. 訪問API,附上得到的 access_token

引入Passport

顯然,自己實現OAuth 流程將需要寫不少東西,且每增加一個API Provider,這個過程需要再來一次。這行里有句黑話:寫得越多,錯得就越多 :)  這時候,應該找個合適的框架

Passport 框架就是為解決類似問題而生的:它以中間件的形式為Node 程序提供身份認證,框架本身將一般形式的認證過程(Basic & Digest/ OAuth/ Open ID)、回調及錯誤處理進行了封裝,而將具體的認證實現抽象為Strategy(策略),與框架本身並無關系,只要是符合Passport 的Strategy都能以插件的形式加入項目被Passport使用。比如基於Google的OAuth 2認證,我們可以用 passport-google-oauth,基於Facebook的OAuth 2認證我們可以用passport-facebook,當然也可以用別的或者自己寫。 這種基於策略的抽象大大簡化了編程模型,所以Passport 有自信稱 " Simple, unobtrusive authentication for Node.js",誠不欺我。

Passport 實現 Google 用戶登錄

Google 作為具體的 API Provider,用Passport 對其進行OAuth 2訪問,需要一個 Strategy 來提供它的交互流程實現,本例中使用 passport-google-oauth

在package.json中確定引用,並用 npm install 安裝模塊:

"dependencies": {
        //... more libraries
        "passport": "*",
        "passport-google-oauth": "*",
    }
 將從 Google Developers Console 獲得的client_id 和 client_secret 存到配置文件中:
{
    //...more configuration
    "GOOGLE_CLIENT_ID" : "xxxx.apps.googleusercontent.com",
    "GOOGLE_CLIENT_SECRET" : "wdsfas3_-safdsafasf",
    "GOOGLE_RETURN_URL" : "http://www.xxx.com/auth/google/return",
}

上篇提到的配置讀取器configUtils 將能自動讀取到它,結合這些實現Google OAuth 2認證(authUtils.js:

var passport = require('passport')
    , GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;
 
var config = require('../configUtils');
 
passport.use(new GoogleStrategy({
        authorizationURL: 'https://accounts.google.com/o/oauth2/auth',
        tokenURL: 'https://accounts.google.com/o/oauth2/token',
        clientID: config.getConfigs().GOOGLE_CLIENT_ID,
        clientSecret: config.getConfigs().GOOGLE_CLIENT_SECRET,
        callbackURL: config.getConfigs().GOOGLE_RETURN_URL
    },
    function (accessToken, refreshToken, profile, done) {
        var userInfo = {
            'type': 'google',
            'userid': profile.id,
            'name': profile.displayName,
            'email': profile.emails[0].value,
            'avatar': profile._json.picture
        };
        return done(null, userInfo);
    }
 
    passport.serializeUser(function (user, done) {
       done(null, user);
    });
 
    passport.deserializeUser(function (obj, done) {
      done(null, obj);
    });
 
));

代碼中的 function (accessToken, refreshToken, profile, done) 就是用戶授權后的回調,Passport 會將獲取到的用戶信息包裝成profile,里面的轉換代碼可以自由發揮,最后記得調用done,並將用戶信息返回。done 就是我們定義在路由里的回調,稍后會提到。

serializeUser 和 deserializeUser 是轉換用的序列化/ 反序列化。

結合Express

在  authUtils.js 中導出給 Express 用的路由:

module.exports = function (app) {
    app.use(passport.initialize());
 
    app.get('/auth/google',
        passport.authenticate('google', {
            scope: 'https://www.google.com/m8/feeds https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile'
        }));
 
    app.get('/auth/google/return',
        passport.authenticate('google', { failureRedirect: '/login' }),
        function (req, res) {
            if (req.user) {
                //...some user login handling code
            }
            res.redirect('/');
        });
};

 第1個 get 路由是我們可以在UI 上引用的地址,點擊它開始OAuth 2 流程,即 Passport 的入口;第2個 get路由是Passport 的出口,即上節提到被 done 方法回調的地址,此時整個 OAuth 2 流程完成,程序得到了 user 信息(Passport將它注入到了 req上,但僅此一次可用,閱后即焚)。在認證過程發生任何錯誤,將跳轉到  failureRedirect 指定的地址。Passport為我們做了諸如 code 交換 token的一系列瑣碎事情。

app.js 中調用,So easy:

var authUtils = require('../authUtils');
authUtils(app);

 

小結

OAuth 2 協議為用戶資源的授權提供了一個安全的、開放而又簡易的標准,但即便如此,在Node.js 的 OAuth 2交互實現中,仍需 為Owner/App/Server的三方交互流程編寫很多代碼。使用Passport框架,將大大簡化這一編程模型,使我們可以將更多精力投入到業務實現上。

本文提供的僅是一個基於Passport框架的OAuth 2方案思路,代碼部分也僅是個骨架,望拋磚引玉。

 

更多文章請移步我的blog新地址: http://www.moye.me/  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM