Part 3: OAuth2進行身份驗證

z.w發表於2015-01-12

Part 2: Google's APIs 和 RequireJS中,我們大概瞭解了Google’s JavaScript APIs。現在我們就開始講講todosAPI,但首先需要一個使用者帳戶.

準備

開始本教程之前,你需要了解以下幾點:

  • alexyoung / dailyjs-backbone-tutorial提交到了9d09a66b1f版本
  • 第二部分中的API key
  • 第二部分中的“Client ID”
  • 更新app/js/config.js成你自己的key(如果你檢出了我的程式碼)

要檢出原始碼,請執行以下命令(或用Git GUI工具):

git clone git@github.com:alexyoung/dailyjs-backbone-tutorial.git
cd dailyjs-backbone-tutorial
git reset --hard 9d09a66b1f

Google’s OAuth 2.0 客戶端 API

開啟app/js/gapi.js並檢視行11至25,這個地方Google提供了一個叫gapi.auth.authorize的方法,它會使用“Client ID” 和scopes去嘗試身份驗證,在app/js/config.js我們設定了scopes:

config.scopes = 'https://www.googleapis.com/auth/tasks https://www.googleapis.com/auth/userinfo.profile';

這將告訴認證系統,我們的應用程式要訪問使用者的配置檔案和Gmail任務。一切準備就緒,但還有兩件事情是需要完成的:handleAuthResult方法和介面的實現.

模板

RequireJS可以通過使用text外掛來載入模板,從GitHub上下載text.js並儲存到app/js/lib/text.js.

這是我的首選技術處理模板.雖然這個應用程式可用index.html一個檔案來實現,從長遠來看把專案分解成更小的模板更易於管理,所以這是一個好主意,習慣了這樣做。

現在開啟app/js/main.js把text外掛新增到RequireJS配置的paths屬性中:

paths: {
  text: 'lib/text'
},

app/js/config.js里加上:

_.templateSettings = {
  interpolate: /\{\{(.+?)\}\}/g
};

這告訴Underscore模板系統使用{{}}插入值,否則稱為插值。

同時還需要目錄來儲存模板和相關檔案:

  • app/js/views – Backbone.js 檢視
  • app/js/templates – 純HTML模板,由views載入
  • app/css

該應用程式index.html檔案需要載入CSS,所以需要新增連結標籤:

<link rel="stylesheet" href="css/app.css">

同時我們新建一個樣式檔案app/css/app.css:

#sign-in-container, #signed-in-container { display: none }

程式剛啟動時我們需要隱藏主體內容和註冊按鈕.OAuth API查詢現有使用者憑據—果使用者如已經登入其資訊將被儲存在cookie,因此views應該進行適當的配置。

在這個階段,模板不是特別明顯優勢,只是先暫存到app/js/templates/app.html

<div class="row-fluid">
  <div class="span2 main-left-col" id="lists-panel">
    <h1>bTask</h1>
    <div class="left-nav"></div>
  </div>
  <div class="main-right-col">
    <small class="pull-right" id="profile-container"></small>
    <div>
      <div id="sign-in-container"></div>
      <div id="signed-in-container">
        <p>You're signed in!</p>
      </div>
    </div>
  </div>
</div>

模板只顯示了sign-in-containersigned-in-container以外的一些元素.

接下來,將下面的貼上到app/js/templates/auth.html:

<a href="#" id="authorize-button" class="btn btn-primary">Sign In with Google</a>

然後auth.html插入到sign-in-container中,這樣是不是很簡單,如此Backbone.js的檢視就相當於得到了擴充套件。

Backbone 檢視

這些模板需要相應的Backbone.js的檢視來進行管理。這部分將演示如何用RequireJS來載入和渲染模板。新建app/js/views/app.js:

define([
  'text!templates/app.html'
],

function(template) {
  var AppView = Backbone.View.extend({
    id: 'main',
    tagName: 'div',
    className: 'container-fluid',
    el: 'body',
    template: _.template(template),

    events: {
    },

    initialize: function() {
    },

    render: function() {
      this.$el.html(this.template());
      return this;
    }
  });

  return AppView;
});

AppView類沒有任何事件,但它確實繫結到了body元素,還通過define(['text!templates/app.html']載入了模板,text!是由RequireJS的 “text” 提供的上面我們新增過了。模板檔案本身就是一個包含相應的HTML的字串。它被繫結到Backbone.View渲染,然後呼叫jQuery的HTML()方法,插入到元素中this.$el.html(this.template());.

AuthView稍微有點不同的是,新建app/js/views/auth.js檔案:

define(['text!templates/auth.html'], function(template) {
  var AuthView = Backbone.View.extend({
    el: '#sign-in-container',
    template: _.template(template),

    events: {
      'click #authorize-button': 'auth'
    },

    initialize: function(app) {
      this.app = app;
    },

    render: function() {
      this.$el.html(this.template());
      return this;
    },

    auth: function() {
      this.app.apiManager.checkAuth();
      return false;
    }
  });

  return AuthView;
});

app物件傳遞給initialize時AuthView被例項化(new AuthView(this)後面).這麼做是因為我們需要讓檢視呼叫從ApiManager獲取所需認證碼.這也可以使用事件,或其他方式來處理—我只是想說明帶值初始化檢視就像任何其他類一樣。

App 核心

檢視要被例項化和渲染.我們需要修改app/js/app.js,使用RequireJS載入檢視:

define([
  'gapi'
, 'views/app'
, 'views/auth'
],

function(ApiManager, AppView, AuthView) {
  var App = function() {
    this.views.app = new AppView();
    this.views.app.render();

    this.views.auth = new AuthView(this);
    this.views.auth.render();

    this.connectGapi();
  }

檔案的其餘部分保持不變,請注意,這些檢視被渲染的順序是非常重要的—AuthView必須在AppView之後.一個更好的方法就是把AuthView放到AppView裡,作為它的依賴.你可以試試這個.

認證實現

app/js/gapi.js檔案中handleAuthResult函式仍然是空得,所以工作尚未完成.下面是處理身份驗證的程式碼:

function handleAuthResult(authResult) {
  var authTimeout;

  if (authResult && !authResult.error) {
    // Schedule a check when the authentication token expires
    if (authResult.expires_in) {
      authTimeout = (authResult.expires_in - 5 * 60) * 1000;
      setTimeout(checkAuth, authTimeout);
    }

    app.views.auth.$el.hide();
    $('#signed-in-container').show();
  } else {
    if (authResult && authResult.error) {
      // TODO: Show error
      console.error('Unable to sign in:', authResult.error);
    }

    app.views.auth.$el.show();
  }
}

this.checkAuth = function() {
  gapi.auth.authorize({ client_id: config.clientId, scope: config.scopes, immediate: false }, handleAuthResult);
};

一個技巧是當使用者已經登入那麼是直接可以看到登入後的資訊的。如果是這樣,那麼認證應不用去處理,否則應提示使用者去登入。

handleAuthResult函式是從checkAuth功能gapi.auth.authorize被呼叫的,這裡沒有顯示這個函式(如果你想看這個函式,你可以在handleAuthResult原始檔中的前找到).this.checkAuth方法不同的是 - 這是一個公共方法,呼叫gapi.auth.authorizeimmediate設定為false,而其他呼叫設定為true.

immediate選項比較重要他決定了是不是彈出一個浮層.當immediate: false時,我們已經登入用這個來檢查它,將顯示彈出一個(是否允許使用者訪問許可權控制的)的浮層:

enter image description here

我們這樣設計主要是基於Google APIs庫javascript版文件:

使用OAuth 2.0 token重新整理頁面,標準情況下authorize()方法總會彈出一個浮層來.Google’s OAuth 2.0實現同樣是支援“immediate”模式的,讓重新整理不出浮層.要使用“immediate”模式,只要新增“immediate: true”到你的登入配置檔案。

我也改變了ApiManager類來儲存對App的引用:

// Near the top of gapi.js
var app;

function ApiManager(_app) {
  app = _app;
  this.loadGapi();
}

相關文章