在 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的計費元件,包括:

  • 資料儲存庫
  • 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. Install the Google Cloud CLI.

  5. If you're using an external identity provider (IdP), you must first sign in to the gcloud CLI with your federated identity.

  6. To initialize the gcloud CLI, run the following command:

    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. Install the Google Cloud CLI.

  9. If you're using an external identity provider (IdP), you must first sign in to the gcloud CLI with your federated identity.

  10. To initialize the gcloud CLI, run the following command:

    gcloud init

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

複製範例應用程式

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

  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 專案:
      • 如果已有專案,請在頁面頂端的「選取機構」下拉式選單中選取。
      • 如果沒有現有 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」(使用者) 頁面。
      前往「使用者」頁面
    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」(供應者) 頁面。
      前往「供應商」頁面
    2. 按一下「Add A Provider」。
    3. 在「選取提供者」下拉式清單中,選取要使用的提供者。
    4. 按一下「已啟用」旁的按鈕,啟用供應商。
      • 如果是第三方身分識別提供者,請從提供者的開發人員網站輸入提供者 ID 和密鑰。Firebase 說明文件在「開始之前」一節中,針對 Facebook、Twitter 和 GitHub 指南提供具體操作說明。
      • 如要整合 SAML 和 OIDC,請參閱 IdP 的設定。
  5. 在 Identity Platform 中將網域新增至授權網域清單:

    1. 前往 Google Cloud 控制台的「Customer Identity」設定頁面。
      前往「設定」頁面
    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() 方法會以 JSON Web Token (JWT) 的形式,傳回 Identity Platform ID 權杖。

在伺服器上驗證憑證

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

// 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 分隔實體。

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

如要在 Datastore 中建立實體,請使用整數或字串等特定屬性,宣告 NDB 模型類別。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] (關閉) 以刪除專案。

後續步驟