在 App Engine 中登入的使用者

本教學課程說明如何使用 Identity Platform、App Engine 標準環境和 Datastore,擷取、驗證及儲存第三方憑證。

本文件將逐步說明如何使用 Firenotes 這個簡易的筆記工具應用程式,將使用者的筆記儲存在自己的個人筆記本中。筆記本會依個別使用者進行儲存,並透過每位使用者的專屬 Identity Platform ID 加以識別。應用程式包含下列元件:

  • 前端會設定登入使用者介面,並擷取 Identity Platform ID。也可處理驗證狀態變更並讓使用者查看其筆記。

  • FirebaseUI 是一套開放原始碼的置入式解決方案,可簡化驗證和 UI 工作。這個 SDK 可處理使用者登入、將多個供應商連結到一個帳戶、還原密碼及其他作業。它採用了驗證的最佳做法,提供流暢安全的登入體驗。

  • 後端會驗證使用者的驗證狀態,並傳回使用者個人資料和其筆記。

這個應用程式使用 NDB 用戶端程式庫將使用者憑證儲存在 Datastore 中,但您可以將憑證儲存在自選資料庫中。

Firenotes 以 Flask 網路應用程式架構為基礎。範例應用程式使用 Flask 是因為它簡單易用。但不論您使用哪一個框架,本文探討的概念和技術皆可適用。

目標

完成本教學課程後,您將完成以下目標:

  • 使用 FirebaseUI 設定 Identity Platform 的使用者介面。
  • 取得 Identity Platform ID 權杖,並使用伺服器端驗證進行驗證。
  • 將使用者憑證和相關資料儲存在 Datastore 中。
  • 使用 NDB 用戶端程式庫查詢資料庫。
  • 將應用程式部署至 App Engine。

費用

本教學課程使用 Google Cloud的計費元件,包括:

  • Datastore
  • Identity Platform

使用 Pricing Calculator,根據您的預測使用量來產生預估費用。

初次使用 Google Cloud 的使用者可能符合免費試用期資格。

事前準備

  1. 安裝 Git、Python 2.7 和 virtualenv。如要進一步瞭解如何設定 Python 開發環境,例如安裝最新版本的 Python,請參閱 設定 Python 開發環境的相關說明 Google Cloud。
  2. 登入 Google Cloud 帳戶。如果您是 Google Cloud新手,歡迎 建立帳戶,親自評估產品在實際工作環境中的成效。新客戶還能獲得價值 $300 美元的免費抵免額,可用於執行、測試及部署工作負載。
  3. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  4. 安裝 Google Cloud CLI。

  5. 若您採用的是外部識別資訊提供者 (IdP),請先使用聯合身分登入 gcloud CLI。

  6. 執行下列指令,初始化 gcloud CLI:

    gcloud init
  7. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  8. 安裝 Google Cloud CLI。

  9. 若您採用的是外部識別資訊提供者 (IdP),請先使用聯合身分登入 gcloud CLI。

  10. 執行下列指令,初始化 gcloud CLI:

    gcloud init

如果您已安裝 SDK 並初始化為其他專案,請將 gcloud 專案設為您用於 Firenotes 的 App Engine 專案 ID。請參閱「管理 Google Cloud SDK 設定」一文,瞭解使用 gcloud 工具更新專案會用到的特定指令。

複製範例應用程式

如要將範例應用程式下載到您的本機電腦上:

  1. 將範例應用程式存放區複製到本機電腦:

    git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git

    您也可以下載 zip 格式的範例檔案,然後將檔案解壓縮。

  2. 前往包含程式碼範例的目錄:

    cd python-docs-samples/appengine/standard/firebase/firenotes
    

新增使用者介面

如何設定 Identity Platform 的 FirebaseUI 並啟用身分識別提供者:

  1. 按照下列步驟將 Identity Platform 新增至應用程式:

    1. 前往Google Cloud 控制台。
      前往 Google Cloud 控制台
    2. 選取要使用的 Google Cloud 專案:
      • 如果您有現成專案,請在頁面頂端的「Select organization」(選取機構) 下拉式清單中選取該專案。
      • 如果沒有現有 Google Cloud 專案,請在Google Cloud 控制台中建立新專案。
    3. 前往 Google Cloud 控制台的「Identity Platform Marketplace」頁面。
      前往 Identity Platform Marketplace 頁面
    4. 在 Identity Platform Marketplace 頁面中,按一下「啟用 Customer Identity」。
    5. 前往 Google Cloud 控制台的 Customer Identity「Users」(使用者) 頁面。
      前往「Users」(使用者) 頁面
    6. 按一下右上方的「應用程式設定詳細資料」。
    7. 將應用程式設定詳細資料複製到網頁應用程式。

      // This code is for illustration purposes only.
      
      // Obtain the following from the "Add Firebase to your web app" dialogue
      // Initialize Firebase
      var config = {
        apiKey: "<API_KEY>",
        authDomain: "<PROJECT_ID>.firebaseapp.com",
        databaseURL: "https://<DATABASE_NAME>.firebaseio.com",
        projectId: "<PROJECT_ID>",
        storageBucket: "<BUCKET>.appspot.com",
        messagingSenderId: "<MESSAGING_SENDER_ID>"
      };
  2. 編輯 backend/app.yaml 檔案,在 env_variables 區段中新增 GOOGLE_CLOUD_PROJECT : 'PROJECT_ID':

    # Copyright 2021 Google LLC
    #
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
    #      http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.
    
    # This code is designed for Python 2.7 and
    # the App Engine first-generation Runtime which has reached End of Support.
    
    runtime: python27
    api_version: 1
    threadsafe: true
    service: backend
    
    handlers:
    - url: /.*
      script: main.app
    
    env_variables:
      GAE_USE_SOCKETS_HTTPLIB : 'true'
    
  3. 在 frontend/main.js 檔案中選取您要提供給使用者的供應商,以設定 FirebaseUI 登入小工具。

    // This code is for illustration purposes only.
    
    // Firebase log-in widget
    function configureFirebaseLoginWidget() {
      var uiConfig = {
        'signInSuccessUrl': '/',
        'signInOptions': [
          // Leave the lines as is for the providers you want to offer your users.
          firebase.auth.GoogleAuthProvider.PROVIDER_ID,
          firebase.auth.FacebookAuthProvider.PROVIDER_ID,
          firebase.auth.TwitterAuthProvider.PROVIDER_ID,
          firebase.auth.GithubAuthProvider.PROVIDER_ID,
          firebase.auth.EmailAuthProvider.PROVIDER_ID
        ],
        // Terms of service url
        'tosUrl': '<your-tos-url>',
      };
    
      var ui = new firebaseui.auth.AuthUI(firebase.auth());
      ui.start('#firebaseui-auth-container', uiConfig);
    }
  4. 在 Google Cloud 控制台啟用您選擇要保留的供應商:

    1. 前往 Google Cloud 控制台的 Customer Identity「Providers」(供應者) 頁面。
      前往「Providers」(供應商) 頁面
    2. 按一下「Add A Provider」。
    3. 在「選取供應商」下拉式清單中,選取要使用的供應商。
    4. 按一下「已啟用」旁的按鈕,啟用供應商。
      • 如果是第三方識別資訊提供者,請輸入取自供應商開發人員網站的供應商 ID 和密碼。Firebase 文件會提供 Facebook、Twitter、GitHub 指南「事前準備」小節中的特定操作說明。
      • 如要整合 SAML 和 OIDC,請參閱 IdP 的設定。
  5. 在 Identity Platform 中將網域新增至已授權網域清單:

    1. 前往 Google Cloud 控制台的「Customer Identity」「Settings」頁面。
      前往「設定」頁面
    2. 在「已授權網域」下方,按一下「新增網域」。
    3. 請用以下格式輸入應用程式的網域:

      [PROJECT_ID].appspot.com
      

      請勿在網域名稱前加上 http://。

安裝依附元件

  1. 前往 backend 目錄,然後完成應用程式設定:

    cd backend/
    
  2. 將依附元件安裝到專案的 lib 目錄中:

    pip install -t lib -r requirements.txt
    
  3. 在 appengine_config.py 中,vendor.add() 方法會註冊 lib 目錄中的程式庫。

在本機執行應用程式

如要在本機執行應用程式,請使用 App Engine 本機開發伺服器:

  1. 在 main.js 中將下列網址新增為 backendHostURL:

    http://localhost:8081

  2. 前往應用程式的根目錄,然後啟動開發伺服器:

    dev_appserver.py frontend/app.yaml backend/app.yaml
    
  3. 使用網路瀏覽器前往 http://localhost:8080/。

在伺服器上驗證使用者

您已設定專案並初始化應用程式以進行開發,現在可以逐步瞭解程式碼,瞭解如何在伺服器上擷取及驗證 Identity Platform ID 權杖。

從 Identity Platform 取得 ID 權杖

伺服器端驗證的第一步是擷取要驗證的存取憑證。驗證要求會透過 Identity Platform 的 onAuthStateChanged() 監聽器處理:

firebase.auth().onAuthStateChanged(function (user) {
  if (user) {
    $('#logged-out').hide();
    var name = user.displayName;

    /* If the provider gives a display name, use the name for the
    personal welcome message. Otherwise, use the user's email. */
    var welcomeName = name ? name : user.email;

    user.getIdToken().then(function (idToken) {
      userIdToken = idToken;

      /* Now that the user is authenicated, fetch the notes. */
      fetchNotes();

      $('#user').text(welcomeName);
      $('#logged-in').show();

    });

  } else {
    $('#logged-in').hide();
    $('#logged-out').show();

  }
});

使用者登入後,回呼中的 Identity Platform getToken() 方法會傳回 Identity Platform ID 憑證,格式為 JSON Web Token (JWT)。

在伺服器上驗證憑證

使用者登入後,前端服務會透過 AJAX GET 要求,擷取使用者記事本中的所有現有記事。這需要授權才能存取使用者資料,因此 JWT 會使用 Bearer 結構,在要求的 Authorization 標頭中傳送:

// Fetch notes from the backend.
function fetchNotes() {
  $.ajax(backendHostUrl + '/notes', {
    /* Set header for the XMLHttpRequest to get data from the web server
    associated with userIdToken */
    headers: {
      'Authorization': 'Bearer ' + userIdToken
    }
  })

在用戶端可以存取伺服器資料之前,伺服器必須先驗證該憑證是否由 Identity Platform 簽署。您可以使用 Python 專用 Google 驗證程式庫來驗證這個權杖。使用驗證資料庫的 verify_firebase_token 函式來驗證不記名憑證及擷取相關宣告:

# This code is for illustration purposes only.

id_token = request.headers["Authorization"].split(" ").pop()
claims = google.oauth2.id_token.verify_firebase_token(
    id_token, HTTP_REQUEST, audience=os.environ.get("GOOGLE_CLOUD_PROJECT")
)
if not claims:
    return "Unauthorized", 401

每個身分識別提供者都會傳送不同的一組聲明,但每組聲明都至少有含有唯一使用者 ID 的 sub 聲明,以及用於提供某些個人資料資訊的聲明,如 name 或 email,可供您用來將應用程式的使用者體驗,進行個人化處理。

管理 Datastore 中的使用者資料

驗證使用者後,您必須儲存他們的資料,才能在登入工作階段結束後保留資料。以下各節說明如何將記事儲存為 Datastore 實體,並依使用者 ID 分隔實體。

建立實體以儲存使用者資料

您可以宣告含有整數或字串等特定屬性的 NDB 模型類別,在 Datastore 中建立實體。Datastore 會依「種類」建立實體索引;以 Firenotes 為例,每個實體的種類都是 Note。 為方便查詢,每個 Note 會和「鍵名稱」一起儲存,這是從上一節的 sub 宣告中取得的使用者 ID。

以下程式碼會示範如何設定實體的屬性,包括在建立實體時,如何使用模型類別的建構函式方法進行設定,以及建立實體後,如何指派個別屬性的方式:

# This code is for illustration purposes only.

data = request.get_json()

# Populates note properties according to the model,
# with the user ID as the key name.
note = Note(parent=ndb.Key(Note, claims["sub"]), message=data["message"])

# Some providers do not provide one of these so either can be used.
note.friendly_id = claims.get("name", claims.get("email", "Unknown"))

如要將新建立的 Note 寫入 Datastore,請針對 note 物件呼叫 put() 方法。

擷取使用者資料

如要擷取與特定使用者 ID 相關的使用者資料,請使用 NDB 的 query() 方法搜尋資料庫,即可取得相同實體群組中的筆記。相同群組中的實體,或祖系路徑,會共用一個鍵名稱,在此範例中是指使用者 ID。

# This code is for illustration purposes only.

def query_database(user_id):
    """Fetches all notes associated with user_id.

    Notes are ordered them by date created, with most recent note added
    first.
    """
    ancestor_key = ndb.Key(Note, user_id)
    query = Note.query(ancestor=ancestor_key).order(-Note.created)
    notes = query.fetch()

    note_messages = []

    for note in notes:
        note_messages.append(
            {
                "friendly_id": note.friendly_id,
                "message": note.message,
                "created": note.created,
            }
        )

    return note_messages

您可以接著擷取查詢資料,並在用戶端顯示筆記:

// This code is for illustration purposes only.

// Fetch notes from the backend.
function fetchNotes() {
  $.ajax(backendHostUrl + '/notes', {
    /* Set header for the XMLHttpRequest to get data from the web server
    associated with userIdToken */
    headers: {
      'Authorization': 'Bearer ' + userIdToken
    }
  }).then(function (data) {
    $('#notes-container').empty();
    // Iterate over user data to display user's notes from database.
    data.forEach(function (note) {
      $('#notes-container').append($('<p>').text(note.message));
    });
  });
}

部署您的應用程式

您已成功整合 Identity Platform 與 App Engine 應用程式。如要查看在即時正式環境中執行的應用程式:

  1. 將 main.js 中的後端主機網址變更為 https://backend-dot-[PROJECT_ID].appspot.com。將 [PROJECT_ID] 替換為專案 ID。
  2. 使用 Google Cloud SDK 指令列介面部署應用程式:

    gcloud app deploy backend/index.yaml frontend/app.yaml backend/app.yaml
    
  3. 前往 https://[PROJECT_ID].appspot.com 查看上線的應用程式。

清除所用資源

如要避免系統向您的 Google Cloud 帳戶收取您在本教學課程中所用資源的相關費用,請刪除您的 App Engine 專案:

刪除專案

如要避免付費,最簡單的方法就是刪除您為了本教學課程所建立的專案。

刪除專案的方法如下:

  1. 前往 Google Cloud 控制台的「Manage resources」(管理資源) 頁面。

    前往「Manage resources」(管理資源)

  2. 在專案清單中選取要刪除的專案,然後點選「Delete」(刪除)。
  3. 在對話方塊中輸入專案 ID,然後按一下 [Shut down] (關閉) 以刪除專案。

後續步驟