六、Firebase 安全和规则

在上一章中,我们了解了如何将访问管理合并到应用程序中,以防止未经授权的访问,这本质上是应用程序级安全。但是,如果我们的数据库没有安全保护怎么办?那么,在这种情况下,数据可能会被未经授权的用户,甚至是授权的用户(如数据库管理员)滥用,从而导致业务损失,有时甚至导致法律诉讼。

数据安全始终是一个主要问题,尤其是当它托管在云服务器上时。我们必须保护数据不受完整性、可用性和机密性的影响。无论您使用的是 RDBMS,如 MySQL 或 MSSQL,还是 NoSQL,如 MongoDB 或 Firebase 实时数据库;必须通过限制对数据的访问来保护所有这些数据库。在本章中,我们将简要介绍常见的数据库安全风险以及防止此类威胁的清单。我们还将看到 Firebase 实时数据库的安全部分和 Firebase 实时数据库规则语言。

以下是我们将在本章中讨论的主题列表:

  • 常见数据库安全风险及防范措施概述
  • Firebase 安全概述
  • Firebase 实时数据库规则概述
  • Firebase 实时数据库规则的结构和定义
  • 数据索引简介
  • 数据库备份和恢复

让我们从安全风险和威胁的预防开始。

安全风险与防范

数据库是任何组织的核心,因为它们包含客户数据和机密业务数据,因此经常成为黑客攻击的目标。在过去几年中发现了一些常见的威胁,包括:

  • 未经授权或无意的活动
  • 恶意软件感染
  • 对数据库服务器的物理损坏
  • 无效数据导致数据损坏
  • 性能下降

为防止此类风险,需要遵循许多协议或安全标准:

  1. 访问控制:包括认证和授权。所有数据库系统都提供访问控制机制,例如使用用户名和密码进行认证。同时,在一些数据库中,设置它不是强制性的,因此有时人们不启用它,导致数据库不安全。类似地,在所有数据库中,都提供了基于角色的安全性等授权机制,以将用户限制在某些数据或数据库中。然而,人们有时会将 root 或 admin 访问权授予所有用户,使数据对所有用户开放。
  2. 审计:审计包括监控所有用户执行的数据库活动,以增强数据的安全性和保护。许多数据库平台包含内置的审计功能,允许您跟踪数据创建、删除或修改活动以及数据库使用情况,以便在早期检测任何可疑活动。
  3. 备份:备份是指从较早的时间恢复数据,并在数据删除或数据损坏时恢复数据。根据需要,备份过程可以是自动的,也可以是手动的。理想情况下,它应该是自动化的,以便可以进行定期备份。虽然至少进行几次备份是值得的,但数据存储空间可能很大,具体取决于数据/备份的大小。为了减少备份的大小,备份文件应该在持久化之前进行压缩。

  4. 数据完整性控制:数据完整性是指数据库中存储的数据的一致性和准确性。数据验证是数据完整性的先决条件。许多关系数据库(RDBMS)通过诸如主键和外键约束之类的约束强制执行数据完整性。对于 NoSQL,数据完整性需要在数据库级别和应用程序级别进行数据验证。

  5. 应用级安全:还需要应用级安全,以防止数据库中保存任何不适当的数据。通常,开发人员在表单级别和业务级别进行验证,以确保他们在数据库中保存有效数据。
  6. 加密:对 SSN 等个人数据或信用卡信息等金融数据进行加密非常重要,以防止误用。通常,SSL 加密用于加密客户端和服务器之间的连接,这本质上是网络级别的安全性,以防止任何恶意攻击者读取此信息。

现在,让我们检查 Firebase 中数据的安全性。

你的火力基地有多安全?

Firebase 基于云存储,所以很明显,人们认为它是否足够安全。不过,无需担心,因为 Firebase 提供了一个安全的体系结构和一组工具来管理应用程序的安全性。Firebase 托管在 SSL(安全套接字层)上,SSL 通常会加密客户端和服务器之间的连接,从而防止网络层的任何数据盗窃或操纵。Firebase 附带了一种基于表达式的规则语言,允许您只需进行配置即可管理数据安全性。

Firebase 安全性完全是基于约定的配置,因此应用程序的安全相关逻辑与业务逻辑是分开的。通过这种方式,它使应用程序松散耦合。

在本章中,我们将学习 Firebase 实时数据库安全性和规则。

实时数据库规则概述

Firebase 数据库规则允许您管理对数据库的读写访问权限。它们还帮助您定义如何验证数据,例如数据是否具有有效的数据类型和格式。如果您的规则允许,则只有这样才能完成读写请求。默认情况下,您的规则设置为仅允许具有对数据库的完全读写访问权限的经过认证的用户。

Firebase 数据库规则具有类似 JavaScript 的语法,有四种类型:

| .read | 它确定是否允许用户读取数据以及何时读取数据。 | | .write | 它确定是否允许用户写入数据以及何时写入数据。 | | .validate | 它验证值的格式是否正确、是否具有子属性及其数据类型。 | | .indexOn | 它确定子级上是否存在索引以支持更快的查询和排序。 |

您可以从 Firebase 控制台中的数据库| |规则选项卡访问和设置规则:

**

Firebase 实时数据库安全有三个步骤:

  1. 用户认证
  2. 用户授权-控制对数据的访问
  3. 验证用户输入

认证

用户认证是保护应用程序不受未经认证的访问的第一步。在第一步中自动识别用户意味着对他们可以访问和操作的数据的限制。在使用后端技术(如 Java、Microsoft.Net 或任何其他平台)的应用程序中,我们编写认证逻辑来限制对应用程序的访问。但是,由于 Firebase 广泛用于仅客户端应用程序,因此我们将没有后端工具的奢侈。幸运的是,Firebase 平台提供了一种认证机制 Firebase Authentication,它内置了对常见认证机制的支持,如基于表单的用户名和密码认证、Google 和 Facebook 登录等。在第 3 章与 Firebase的认证、第 5 章用户配置文件和访问管理中,我们已经看到了如何实现 Firebase 认证。以下规则规定,要访问数据库,必须对用户进行认证。它还指定一旦用户通过认证,就可以访问数据库中的所有可用数据:

{
  "rules": {
    ".read": "auth != null",
    ".write": "auth != null"
  }
}

批准

一旦用户通过认证,他们就可以访问数据库。但是,您需要对谁可以访问什么进行一些控制。不应允许每个人读取/写入数据库中的所有数据。这就是授权的关键所在。Firebase 数据库规则允许您控制每个用户的访问。Firebase 安全规则基于节点,由单个 JSON 对象管理,您可以在实时数据库控制台上或使用 Firebase CLI 编辑:

{
  "rules": {
        "users": { 
           ".read": "true",
           ".write": "false"
        }
  }
}

前面的规则确定所有用户都能够读取用户的数据,但没有人能够写入数据。另外,请注意,必须将rules作为安全 JSON 对象中的第一个节点。

以下是指定用户专用数据的规则示例:

{
  "rules": {
    "users": {
      "$uid": {
        ".read": "$uid === auth.uid",
        ".write": "$uid === auth.uid"
      }
    }
  }
}

现在,您可能会有一个问题,比如我们有嵌套的数据结构,规则将如何应用于该数据。为了回答这个问题,这里需要记住的一点是,.read.write规则级联含义;授予父节点的读写访问权总是授予所有子节点的读写访问权。

The rules at parent node have higher priority and hence they will override the rules defined at its child level.

Firebase 规则还提供一些内置变量和函数,允许您访问 Firebase 认证信息、引用其他路径等。我们将在本章接下来的章节中详细检查这一点。

数据验证

如引言部分所示,我们需要在将数据保存到数据库之前验证数据,以保持数据的完整性和正确性。Firebase 规则提供.validate表达式,如.read.write以实现验证逻辑,如字段长度应仅为这么多字符或必须为字符串数据类型。

考虑这个例子:

{
  "rules": {
        "users": { 
             "email": {
                    ".validate": "newData.isString() && newData.val().length < 50"
              }
        }
  }
}

前面的电子邮件字段验证规则确定电子邮件字段的值必须是字符串,并且其长度应小于 30 个字符。

需要注意的是,验证规则不会级联,因此为了允许写入,所有相关的验证规则都必须计算为 true。

既然我们对 Firebase 规则有了基本的了解,那么让我们深入了解规则配置。

规则定义和结构

Firebase 规则提供可在规则定义内使用的预定义变量:

| 名称 | 定义/用法 | | auth | 它表示经过认证的用户的信息。对于未经认证的用户,它将为空。它是一个包含 uid、令牌和提供程序字段以及相应值的对象。 | | $ variables | 它表示通配符路径,以引用动态生成的键并表示 ID。 | | root | 它表示在应用给定数据库操作之前,Firebase 数据库根路径上的数据快照。 | | data | 它表示应用给定数据库操作之前的数据快照。例如,在更新或写入的情况下,根表示原始数据快照,而不包含更新或写入中的更改。 | | newData | 它表示应用给定数据库操作之前的数据快照。但是,它既包括现有数据,也包括新数据,其中包括由给定数据操作操作的数据。 | | now | 它表示当前时间(以毫秒为单位),即自 1970 年年月 1 日【UTC 午夜】以来经过秒数。** |

在下一节中,我们将了解如何在规则中使用这些预定义变量。

正如我们在授权部分所看到的,我们需要了解规则将如何应用于嵌套数据。经验法则是,我们需要根据数据库中数据的结构来构造规则。

我们将扩展本书第 5 章用户配置文件和访问管理中开发的帮助台应用程序。

我们的数据结构如下:

"helpdesk" : {
    "tickets" : {
      "FlQefqueU2USLElL4vc5MoNUnu03" : {
        "-L4L1BLYiU-UQdE6lKA_" : {
          "comments" : "Need extra 4GB RAM in my system",
          "date" : "Fri Feb 02 2018 15:51:10 GMT+0530 (India Standard 
           Time)",
          "department" : "IT",
          "email" : "harmeet_15_1991@yahoo.com",
          "issueType" : "Hardware Request",
          "status" : "progress"
        }
      },
      "KEEyErkmP3YE1BagxSci0hF0g8H2" : {
        "-L4K01hUSDzPXTIXY9oU" : {
          "comments" : "Not able to access my email",
          "date" : "Fri Feb 02 2018 11:06:32 GMT+0530 (India Standard 
           Time)",
          "department" : "IT",
          "email" : "harmeetsingh090@gmail.com",
          "issueType" : "Email Related Issues",
          "status" : "progress"
        }
      },
      "all" : {
        "-L4K01hUSDzPXTIXY9oU" : {
          "comments" : "Not able to access my email",
          "date" : "Fri Feb 02 2018 11:06:32 GMT+0530 (India Standard 
           Time)",
          "department" : "IT",
          "email" : "harmeetsingh090@gmail.com",
          "issueType" : "Email Related Issues",
          "status" : "progress"
        },
        "-L4L1BLYiU-UQdE6lKA_" : {
          "comments" : "Need extra 4GB RAM in my system",
          "date" : "Fri Feb 02 2018 15:51:10 GMT+0530 (India Standard 
           Time)",
          "department" : "IT",
          "email" : "harmeet_15_1991@yahoo.com",
          "issueType" : "Hardware Request",
          "status" : "progress"
        }
      }
    }
  }

在这里,我们可以看到,为了在用户级别保护数据,为了仅显示与登录用户相关的票据,我们将它们存储在 userId 下,例如FlQefqueU2USLElL4vc5MoNUnu03KEEyErkmP3YE1BagxSci0hF0g8H2,为了向管理员显示所有票据,我们将它们存储在all下。然而,这不是理想的解决方案,因为它有两个问题:数据是冗余的,要更新任何数据,我们必须在两个地方进行更新。幸运的是,我们可以通过规则直接在数据库中处理这种安全性。

我们将更改数据,并从数据中删除all节点。我们还将在$userId下添加一个变量,以标识用户是否为管理员。所以它看起来是这样的:

"helpdesk" : {
    "tickets" : {
      "FlQefqueU2USLElL4vc5MoNUnu03" : {
        "-L4L1BLYiU-UQdE6lKA_" : {
          "comments" : "Need extra 4GB RAM in my system",
          "date" : "Fri Feb 02 2018 15:51:10 GMT+0530 (India Standard 
           Time)",
          "department" : "IT",
          "email" : "harmeet_15_1991@yahoo.com",
          "issueType" : "Hardware Request",
          "status" : "progress"
        },
         "isAdmin": true
      },
      "KEEyErkmP3YE1BagxSci0hF0g8H2" : {
        "-L4K01hUSDzPXTIXY9oU" : {
          "comments" : "Not able to access my email",
          "date" : "Fri Feb 02 2018 11:06:32 GMT+0530 (India Standard 
           Time)",
          "department" : "IT",
          "email" : "harmeetsingh090@gmail.com",
          "issueType" : "Email Related Issues",
          "status" : "progress"
        },
        "isAdmin": false
      }
    }
  }
}

我们的规则如下:

{
  "rules": {
     "helpdesk": {
      "tickets": {
        ".read": "data.child(auth.uid).child('isAdmin').val()==true",
        ".write": "data.child(auth.uid).child('isAdmin').val()==true",
        "$uid": {
          ".read": "auth.uid == $uid",
          ".write": "auth.uid == $uid"
        }
      }
     }

  }
}

这些规则本质上施加了一些限制,即如果用户是 Admin,即isAdmin为 true,则他们可以读取和写入所有数据。但是,其他用户只能读取/写入自己的数据。

这里,我们还使用了预定义的变量数据,它表示在应用write操作之前的DataSnapshot。同样,我们可以使用root变量来引用根路径,newData来引用写入操作后将存在的数据快照。****

****现在,如果您已经观察到,我们使用了.child,它基本上用于引用任何子路径/属性。在我们的规则中,我们正在检查$uidisAdmin的值是否为真,因为我们希望将所有数据的访问权授予管理员。同样,我们可以在规则中使用任何数据作为条件。

另外,这里需要注意的一点是,一旦我们在父级tickets定义了.read.write规则,我们就不会在$uid下检查isAdmin条件,因为规则会级联,所以一旦您授予管理员读/写权限,您不需要在$uid级别重复这些条件。同时,需要注意的是,必须在父位置定义规则。如果我们不在父位置定义它们,即使子路径是可访问的,您的数据操作也将完全失败。

例如,在下面的规则中,我们可以看到,虽然我们在票证级别有访问权限,但是由于我们没有在$uid级别定义规则,我们将无法访问数据:

{
  "rules": {
     "helpdesk": {
      "tickets": {
        "$ticketId": {
          ".read": true,
          ".write": true
        }
      }
     }

  }
}

基于查询的规则

如前一示例所示,规则不能用作筛选器。然而,有时,我们需要根据某些条件或查询参数,只允许访问数据的子集。例如,假设我们只需要返回查询结果集中 1000 条记录中的前 100 条记录。我们可以通过使用查询来实现这一点。根据查询参数为结果集提供读写访问权限的表达式:

tickets: {
  ".read": "query.limitToFirst <= 100"
}

前面的命令将允许访问默认情况下按键排序的前 100 条记录。如果您想指定orderByChild,也可以这样做,如下所示:

tickets: {
  ".read": "query.orderByChild == 'department' && query.limitToFirst <= 100"
}

请确保您在读取数据时指定了orderByChild,否则您的读取将失败。

数据索引

Firebase 允许您使用子键编写查询。为了提高查询性能,可以使用.indexOn规则在这些键上定义索引。我们假设您已经知道索引是如何工作的,因为几乎所有数据库系统都支持索引。

让我们举个例子来更好地理解这一点。假设在我们的帮助台系统中,我们经常按部门密钥订购票据,并且我们正在使用orderbyChild()

{
  "rules": {
     "helpdesk": {
           "tickets": {
               ".indexOn": ["department"]
            }
      }
  }
}

同样,如果我们使用orderByValue(),我们可以有以下规则:

".indexOn": ".value"

备份

在本章的第一节中,我们了解了管理数据备份的重要性。虽然您可以手动进行和维护数据备份,但也有可能会丢失某些内容并丢失备份。幸运的是,Firebase 提供了一个自动备份服务,可以设置为每天自动备份数据和规则。请注意,此服务仅适用于 Blaze plan 用户,并将按照标准费率收费。您可以在查看各种可用的订阅计划 https://firebase.google.com/pricing/

安装程序

您可以从 Firebase“实时数据库”部分的“备份”选项卡设置数据库备份。安装向导将指导您完成配置自动备份的步骤。您的数据库备份活动将在每天的特定时间进行,而不会影响负载,并确保为所有备份客户提供最高可用性。

此外,您还可以在需要获取数据和规则的时间点快照时进行手动备份。

您的备份将存储在谷歌云存储中,这是谷歌云平台提供的对象存储服务。从本质上讲,谷歌云存储提供的存储桶类似于计算机文件系统上的目录,您的备份将存储在其中。因此,安装完成后,将创建一个具有 Firebase 写入数据权限的存储桶。我们将在第 8 章Firebase 云存储中详细介绍谷歌云存储和 Firebase 云存储。

备份服务使用 Gzip 压缩自动压缩备份文件,从而减少总体备份大小,最终降低成本,并最大限度地减少数据传输时间。压缩文件大小因数据库中的数据而异,但通常情况下,它会将总体文件大小减小到原始解压缩文件大小的 1/3。您可以根据需要启用和禁用 Gzip 压缩。

为了进一步节省成本,您还可以在 bucket 上启用 30 天的生命周期策略来删除较旧的备份;例如,超过 30 天的备份会自动删除。

您的 gzip JSON 文件可以通过使用gunzip二进制文件执行以下命令行命令来解压缩,该二进制文件在 OS-X 和大多数 Linux 发行版上默认可用:

gunzip <DATABASE_NAME>.json.gz

将根据以下命名约定生成文件名。它将有一个时间戳(ISO 8601 标准):

Database data: YYYY-MM-DDTHH:MM:SSZ_<DATABASE_NAME>_data.json
Database rules: YYYY-MM-DDTHH:MM:SSZ_<DATABASE_NAME>_rules.json

如果启用了 Gzip 压缩,则名称将附加一个.gz后缀。

考虑这个例子:

 Database data: YYYY-MM-DDTHH:MM:SSZ_<DATABASE_NAME>_data.json.gz
Database rules: YYYY-MM-DDTHH:MM:SSZ_<DATABASE_NAME>_rules.json.gz

备份完成后,您可能希望在某个时间点恢复它。让我们检查一下如何从备份中恢复数据。

从备份恢复

要从备份中恢复数据,请首先从 Google Cloud Storage 下载备份文件,并按照前面的命令进行解压缩。获得 JSON 文件后,可以通过以下两种方式之一导入数据:

  • 在 Firebase 控制台的数据库部分,您将找到一个导入 JSON按钮,该按钮将允许您上传文件。 * 您可以使用 CURL 命令:curl 'https://<DATABASE_NAME>.firebaseio.com/.json?auth=<SECRET>&print=silent' -x PUT -d @<DATABASE_NAME>.json。请注意,您需要分别用自己的值替换DATABASE_NAMESECRET。您可以从“数据库设置”页面获取机密。

*# 总结

本章介绍数据的常见安全威胁,特别是当数据驻留在云上时,以及我们如何保护数据。它还解释了 Firebase 是安全的,只要我们在数据库中定义适当的规则并控制授权用户对数据的访问,我们就不应该太担心数据的安全性。

Firebase 托管在安全服务器层上,该层管理传输层的安全性。它还为您提供了一个功能强大但简单的规则引擎,可以对其进行配置以保护您的数据,同时还提供了分离关注点的好处—将安全逻辑与应用程序逻辑分离。

我们还详细了解了安全规则,以及如何使用简单的类似 JavaScript 的语法来定义它们。

在下一章中,我们将探讨 Firebase 云消息传递和云函数。*