在 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. 安裝 GitPython 2.7 virtualenv。如要進一步瞭解如何設定 Python 開發環境,例如安裝最新版本的 Python,請參閱 替 Google Cloud設定 Python 開發環境
  2. Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
  3. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  4. Install the 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.

    Go to project selector

  8. Install the Google Cloud CLI.

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

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

    gcloud init
  11. 如果您已安裝 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」(使用者) 頁面
      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 文件會提供 FacebookTwitterGitHub 指南「事前準備」小節中的特定操作說明。
        • 如要整合 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 要求,擷取使用者記事本中的所有現有記事。這項操作需要授權才能存取使用者資料,因此系統會使用 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 聲明,以及用於提供某些個人資料資訊的聲明,如 nameemail,可供您用來將應用程式的使用者體驗,進行個人化處理。

    管理 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. In the Google Cloud console, go to the Manage resources page.

      Go to Manage resources

    2. In the project list, select the project that you want to delete, and then click Delete.
    3. In the dialog, type the project ID, and then click Shut down to delete the project.

    後續步驟

    • 探索 Google Cloud 的參考架構、圖表和最佳做法。 歡迎瀏覽我們的雲端架構中心