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

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

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

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

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

    FirebaseUI

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

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

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

架构图

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

目标

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

费用

本教程使用 Cloud Platform 的收费组件,包括:

  • Google Cloud Datastore

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

准备工作

  1. 安装 GitPython 2.7 virtualenv。如需详细了解如何设置 Python 开发环境,例如安装最新版本的 Python,请参阅为 Google Cloud Platform 设置 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
    

添加 Firebase 身份验证界面

要配置 FirebaseUI 并启用身份提供方,请执行以下操作:

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

    1. Firebase 控制台中创建一个 Firebase 项目。
      • 如果您当前没有 Firebase 项目,请点击添加项目,然后输入现有的 Google Cloud Platform 项目名称或新项目名称。
      • 如果您有一个打算使用的现有 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>"
      };

  2. 修改 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>'
    

  3. 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);
    }

  4. 通过点击身份验证 >登陆方法,启用您选择保留在 Firebase 控制台中的提供方。然后,在登录提供方下,将光标悬停在提供商上,然后点击铅笔图标。

    登录提供方

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

      切换“启用”按钮

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

      [PROJECT_ID].appspot.com
      

      不要在域名前包含 http://

安装依赖项

导航到 backend 目录并完成应用设置:

Mac OS/Linux

  1. 在项目以外的目录中创建一个独立的 Python 环境并将其激活:
    virtualenv env
    source env/bin/activate
  2. 导航到项目目录并安装依赖项:
    cd YOUR_PROJECT
    pip install -t lib -r requirements.txt

Windows

如果您已安装 Cloud SDK,则应该也已安装 Python 2.7,通常位于 C:\python27_x64\(对于 64 位系统)。使用 Powershell 运行 Python 包。

  1. 找到 Powershell 安装。
  2. 右键点击 Powershell 的快捷方式,并以管理员身份启动。
  3. 尝试运行 python 命令。如果未找到,请将 Python 文件夹添加到环境的 PATH
    $env:Path += ";C:\python27_x64\"
  4. 在项目以外的目录中创建一个独立的 Python 环境并将其激活:
    python -m virtualenv env
    env\Scripts\activate
  5. 导航到项目目录并安装依赖项:
    cd YOUR_PROJECT
    python -m pip install -t lib -r requirements.txt

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 上实时查看应用。

清理

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

删除项目

避免支付费用的最简单方法是删除您为本教程创建的项目。

要删除该项目,请执行以下操作:

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

    转到“项目”页面

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

后续步骤

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

发送以下问题的反馈:

此网页
App Engine standard environment for Python 2