使用 Firebase 在 App Engine 上对用户进行身份验证


本教程介绍如何使用 Firebase 身份验证、App Engine 标准环境和 Datastore 来检索、验证和存储用户凭据。

本文档将介绍一款名为 Firenotes 的简单笔记应用,该应用可将用户笔记存储在用户自己的个人笔记本中。笔记本按用户存储,并由每个用户唯一的 Firebase 身份验证 ID 进行标识。该应用包含以下组件:

  • 前端 - 前端负责配置登录界面并检索 Firebase 身份验证 ID。它还会处理身份验证状态变更并允许用户查看其笔记。

  • FirebaseUI - 是一个具有普适性的开源解决方案,可简化身份验证和界面任务。SDK 会处理用户登录、将多个提供商关联到一个账号、恢复密码等。FirebaseUI 会实施身份验证最佳做法,可带来流畅、安全的登录体验。

    FirebaseUI
  • 后端 - 后端负责验证用户的身份验证状态,并返回用户个人资料信息以及用户的笔记。

该应用使用 NDB 客户端库在 Datastore 中存储用户凭据,但您可以将凭据存储在您选择的数据库中。

下图显示了前端与后端如何相互通信以及用户凭据如何从 Firebase 传输到数据库。
使用用户凭据的请求的路径

Firenotes 基于 Flask 网络应用框架。该示例应用之所以使用 Flask,是因为该框架简单易用,但无论您使用哪种框架,此处所探讨的概念和技术都适用。

目标

学完本教程后,您将可以完成以下任务:

  • 配置 Firebase 身份验证界面。
  • 获取 Firebase ID 令牌并使用服务器端身份验证对其进行验证。
  • 将用户凭据和关联的数据存储到 Datastore 中。
  • 使用 NDB 客户端库查询数据库。
  • 将应用部署到 App Engine。

费用

本教程使用 Google Cloud 的以下收费组件:

  • Datastore

您可使用价格计算器根据您的预计使用情况来估算费用。Google Cloud 新用户可能有资格申请免费试用

准备工作

  1. 安装 GitPython 2.7virtualenv。如需详细了解如何设置 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. To initialize the gcloud CLI, run the following command:

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

    Go to project selector

  7. Install the Google Cloud CLI.
  8. 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
    
    要配置 FirebaseUI 并启用身份提供商,请执行以下操作:

  3. 按照以下步骤将 Firebase 添加到您的应用:

    1. Firebase 控制台中创建一个 Firebase 项目。
      • 如果您当前没有 Firebase 项目,请点击添加项目,然后输入现有 Google Cloud 项目名称或新项目名称。
      • 如果您有一个打算使用的现有 Firebase 项目,请从控制台中选择该项目。
    2. 在项目概览页面中,点击将 Firebase 添加到您的网页应用。如果您的项目已有应用,请从项目概览页面中选择添加应用
    3. 使用项目自定义代码段的 Initialize Firebase 部分填写 frontend/main.js 文件的以下部分:

      // 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>"
      };
  4. frontend/main.js 文件中,通过选择要向用户提供的提供商来配置 FirebaseUI 登录微件

    // 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);
    }
  5. 依次点击 Authentication(身份验证)> Sign-in method(登录方法),启用您选择要保留在 Firebase 控制台中的提供商。然后,在登录提供商下,将光标悬停在提供商上,然后点击铅笔图标。

    登录提供商

    1. 切换启用按钮,对于第三方身份提供商,请输入提供商开发者网站上的提供商 ID 和密钥。Firebase 文档会提供 FacebookTwitterGitHub 指南“准备工作”部分中的具体说明。启用提供商后,点击保存

      切换启用按钮

    2. 在 Firebase 控制台中,点击已获授权的网域下的添加网域,然后按以下格式在 App Engine 上输入应用的网域:

      [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/

在服务器上对用户进行身份验证

现在您已设置了一个项目并初始化了一个用于开发的应用,接下来您可以浏览代码,以了解如何在服务器上检索和验证 Firebase ID 令牌。

从 Firebase 获取 ID 令牌

服务器端身份验证的第一步是检索要验证的访问令牌。身份验证请求通过 Firebase 中的 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();

  }
});

用户登录后,回调中的 Firebase getToken() 方法将以 JSON 网络令牌 (JWT) 形式返回 Firebase 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
    }
  })

在客户端可以访问服务器数据之前,您的服务器必须验证令牌是否已由 Firebase 签名。您可以使用 Python 版 Google 身份验证库验证此令牌。 使用身份验证库的 verify_firebase_token 函数验证不记名令牌并提取声明:

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。

以下代码演示了如何设置实体的属性(在创建实体时,通过模型类的构造函数方法进行设置;在创建实体后,通过分配各个属性进行设置):

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。

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

然后,您可以提取查询数据并在客户端显示笔记:

// 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));
    });
  });
}

部署应用

您已成功地将 Firebase 身份验证与 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.

后续步骤