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

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

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

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

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

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

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

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

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

目标

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

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

费用

本教程使用 Google Cloud Platform (GCP) 的计费组件,包括:

  • Cloud Datastore

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

准备工作

  1. 安装 GitPython 2.7virtualenv。如需详细了解如何设置 Python 开发环境(例如安装最新版本的 Python),请参阅为 GCP 设置 Python 开发环境
  2. 登录您的 Google 帐号。

    如果您还没有 Google 帐号,请注册新帐号

  3. 选择或创建 Google Cloud Platform 项目。

    转到“管理资源”页面

  4. 安装并初始化 Cloud SDK

如果您已为其他项目安装并初始化 SDK,请将 gcloud 项目设置为您用于 Firenotes 的 App Engine 项目 ID。如需了解使用 gcloud 工具更新项目需要用到哪些特定命令,请参阅管理 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 Platform 项目名称或新项目名称。
      • 如果您有一个打算使用的现有 Firebase 项目,请从控制台中选择该项目。
    2. 在项目概览页面中,点击将 Firebase 添加到您的 Web 应用。如果您的项目已经有应用,请从项目概览页面中选择添加应用
    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. 修改 backend/app.yaml 文件,并在环境变量中输入您的 Firebase 项目 ID:

    runtime: python27
    api_version: 1
    threadsafe: true
    service: backend
    
    handlers:
    - url: /.*
      script: main.app
    
    env_variables:
      # Replace with your Firebase project ID.
      FIREBASE_PROJECT_ID: '<PROJECT_ID>'
    
  5. 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);
    }
  6. 依次点击 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.getToken().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 架构将 JWT 发送到该请求的 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
    }
  })

在客户端可以访问服务器数据之前,您的服务器必须验证令牌是否已由 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)
if not claims:
    return 'Unauthorized', 401

每个身份提供商都会发送一组不同的声明,但每组声明都至少包含一个带有唯一用户 ID 的 sub 声明,以及用来提供一些个人资料信息的声明(例如 nameemail),您可以利用这些信息让您的应用带来个性化的用户体验。

在 Cloud Datastore 中管理用户数据

在对用户进行身份验证之后,您需要存储用户的数据,以便这些数据在登录会话结束后保留下来。以下部分介绍了如何将每份笔记存储为一个 Cloud Datastore 实体,并按用户 ID 将这些实体隔离开来。

创建实体以存储用户数据

您可以通过声明具有某些属性(如整数或字符串)的 NDB 模型类,在 Cloud Datastore 中创建实体。Cloud 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 写入到 Cloud 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. 使用 Cloud SDK 命令行界面部署应用:

    gcloud app deploy backend/index.yaml frontend/app.yaml backend/app.yaml
    
  3. https://[PROJECT_ID].appspot.com 上实时查看应用。

清理

为避免系统因本教程中使用的资源向您的 GCP 帐号收取费用,请删除您的 App Engine 项目:

删除项目

若要避免产生费用,最简单的方法是删除您为本教程创建的项目。

如需删除项目,请执行以下操作:

  1. 在 GCP Console 中,转到项目页面。

    转到“项目”页面

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

后续步骤

此页内容是否有用?请给出您的反馈和评价:

发送以下问题的反馈:

此网页
适用于 Python 2 的 App Engine 标准环境