使用 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. 登录您的 Google Cloud 账号。如果您是 Google Cloud 新手,请创建一个账号来评估我们的产品在实际场景中的表现。新客户还可获享 $300 赠金,用于运行、测试和部署工作负载。
  3. 在 Google Cloud Console 中的项目选择器页面上,选择或创建一个 Google Cloud 项目

    转到“项目选择器”

  4. 安装 Google Cloud CLI。
  5. 如需初始化 gcloud CLI,请运行以下命令:

    gcloud init
  6. 在 Google Cloud Console 中的项目选择器页面上,选择或创建一个 Google Cloud 项目

    转到“项目选择器”

  7. 安装 Google Cloud CLI。
  8. 如需初始化 gcloud CLI,请运行以下命令:

    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. 在 Google Cloud 控制台中,进入管理资源页面。

    转到“管理资源”

  2. 在项目列表中,选择要删除的项目,然后点击删除
  3. 在对话框中输入项目 ID,然后点击关闭以删除项目。

后续步骤