在 Firebase Cloud Storage 安全规则中使用条件

本指南以学习 Firebase 安全规则语言的核心语法指南为基础,介绍如何在面向 Cloud Storage 的 Firebase 安全规则中添加条件。

Cloud Storage 安全规则的主要构成要素是条件。条件是一个布尔表达式,用于确定应该允许还是拒绝执行特定操作。对于基本规则,最好使用 truefalse 字面量作为条件。不过,“面向 Cloud Storage 的 Firebase 安全规则”语言为您提供了编写更复杂条件的方法,这些条件可以:

  • 检查用户身份验证
  • 验证传入数据

身份验证

面向 Cloud Storage 的 Firebase 安全规则与 Firebase Authentication 集成,可为 Cloud Storage 提供基于用户的强大身份验证功能。这样就可以根据 Firebase Authentication 令牌的声明进行精确的访问权限控制。

当经过身份验证的用户对 Cloud Storage 发出请求时,系统将使用该用户的 uid (request.auth.uid) 以及 Firebase Authentication JWT (request.auth.token) 的声明来填充 request.auth 变量。

另外,当使用自定义身份验证时,request.auth.token 字段中会显示其他声明。

当未经身份验证的用户发出请求时,request.auth 变量为 null

使用这些数据时,可以通过几种常用方法来利用身份验证保护文件:

  • 公开:忽略 request.auth
  • 仅对经过身份验证的用户公开:确定 request.auth 不为 null
  • 用户私有:检查 request.auth.uid 是否等于路径 uid
  • 群组私有:检查自定义令牌的声明是否与所选的声明相匹配,或读取文件元数据以确定某个元数据字段是否存在

公开

任何不考虑 request.auth 上下文的规则均可被视为 public 规则,因为它不考虑用户的身份验证上下文。这些规则适合用于呈现公开数据(如游戏资源、声音文件或其他静态内容)的场景。

// Anyone to read a public image if the file is less than 100kB
// Anyone can upload a public file ending in '.txt'
match /public/{imageId} {
  allow read: if resource.size < 100 * 1024;
  allow write: if imageId.matches(".*\\.txt");
}

仅对经过身份验证的用户公开

在某些情况下,您可能希望允许所有经过身份验证的应用用户查看数据,但不允许未经身份验证的用户查看。由于所有未经身份验证的用户的 request.auth 变量均为 null,因此如果要求必须通过身份验证,您只需检查 request.auth 变量是否存在即可:

// Require authentication on all internal image reads
match /internal/{imageId} {
  allow read: if request.auth != null;
}

用户私有

到目前为止,request.auth 最常见的使用场景是为个人用户提供对其文件的精细权限,例如上传个人资料照片和读取私人文件等。

因为 Cloud Storage 中的文件具有完整的文件“路径”,所以若要让用户拥有某个文件的控制权限,只需要在文件名前缀中包含一个唯一的用户识别信息(例如用户的 uid),在对规则求值时可以检查此信息:

// Only a user can upload their profile picture, but anyone can view it
match /users/{userId}/profilePicture.png {
  allow read;
  allow write: if request.auth.uid == userId;
}

群组私有

另一个同样常见的使用场景是为群组提供对象权限,例如允许多名团队成员协同撰写或修改某个共享的文档。下列几种方法可以实现这个目的:

  • 创建一个 Firebase Authentication 自定义令牌,其中包含有关群组成员的附加信息(例如群组 ID)
  • 文件元数据中包含群组信息(例如群组 ID 或经过授权的 uid 的列表)

将这些数据存储在令牌或文件元数据中后,就可以在规则中引用这些数据:

// Allow reads if the group ID in your token matches the file metadata's `owner` property
// Allow writes if the group ID is in the user's custom token
match /files/{groupId}/{fileName} {
  allow read: if resource.metadata.owner == request.auth.token.groupId;
  allow write: if request.auth.token.groupId == groupId;
}

请求求值

上传、下载、元数据更改和删除操作是使用发送到 Cloud Storage 的 request 进行求值的。除了如上所述的 request.auth 对象中的用户唯一 ID 和 Firebase Authentication 载荷外,request 变量还包含正在执行请求的文件路径、请求接收时间以及新的 resource 值(如果请求为写入操作)。

request 对象还包含用户的唯一 ID 和 request.auth 对象中的 Firebase Authentication 载荷,我们将在相关文档的基于用户的安全性部分进一步阐述这一点。

request 对象中属性的完整列表如下:

属性 类型 说明
auth 映射<字符串, 字符串> 如果用户已登录,此属性将提供 uid(用户的唯一 ID)和 token(Firebase Authentication JWT 声明的映射)。如果用户未登录,则其值为 null
params 映射<字符串, 字符串> 包含请求的查询参数的映射。
path 路径 path 表示正在执行请求的路径。
resource 映射<字符串, 字符串> 新资源值,仅当请求为 write 操作时存在。
time 时间戳 时间戳,表示对请求进行评估时的服务器时间。

资源求值

对规则求值时,您可能还需要对正在上传、下载、修改或删除的文件的元数据求值。这样一来,您就可以创建功能强大的复杂规则来执行任务,例如仅允许上传包含特定内容类型的文件,或仅允许删除超过特定大小的文件。

面向 Cloud Storage 的 Firebase 安全规则在 resource 对象中提供文件元数据,其中包含 Cloud Storage 对象中提供的元数据键值对。可以在 readwrite 请求中检查这些属性,以确保数据完整性。

write 请求(如上传、元数据更新和删除请求)中,除了 resource 对象(其中包含请求路径下现有文件的文件元数据)外,您还可以使用 request.resource 对象,其中包含在允许写入时要写入的一部分文件元数据。您可以使用这两个值来确保数据完整性,或强制实施应用限制(如文件类型或大小)。

resource 对象中属性的完整列表如下:

属性 类型 说明
name 字符串 此对象的完整名称
bucket 字符串 此对象所在存储桶的名称。
generation 整数 此对象的 Google Cloud Storage 对象世代
metageneration 整数 此对象的 Google Cloud Storage 对象元世代
size 整数 此对象的大小(以字节为单位)。
timeCreated 时间戳 表示此对象的创建时间的时间戳。
updated 时间戳 表示此对象的最后更新时间的时间戳。
md5Hash 字符串 此对象的 MD5 哈希。
crc32c 字符串 此对象的 crc32c 哈希。
etag 字符串 与此对象相关联的 etag。
contentDisposition 字符串 与此对象相关联的内容处置。
contentEncoding 字符串 与此对象相关联的内容编码。
contentLanguage 字符串 与此对象相关联的内容语言。
contentType 字符串 与此对象相关联的内容类型。
metadata 映射<字符串, 字符串> 开发者指定的其他自定义元数据的键值对。

request.resource 包含上述除 generationmetagenerationetagtimeCreatedupdated 外的所有属性。

使用 Cloud Firestore 增强安全性

您可以使用 Cloud Firestore 中的文档来对更多授权条件进行评估。

利用 firestore.get()firestore.exists() 函数,您的安全规则可以针对 Cloud Firestore 中的文档评估传入请求。firestore.get()firestore.exists() 函数都需要指定完整的文档路径。使用变量为 firestore.get()firestore.exists() 构建路径时,您需要使用 $(variable) 语法对变量进行明确转义。

下面的示例展示了一条规则,规定只有特定俱乐部的成员才能读取文件。

service firebase.storage {
  match /b/{bucket}/o {
    match /users/{club}/files/{fileId} {
      allow read: if club in
        firestore.get(/databases/(default)/documents/users/$(request.auth.id)).memberships
    }
  }
}
在下一个示例中,则只有用户的好友才能查看他们的照片。
service firebase.storage {
  match /b/{bucket}/o {
    match /users/{userId}/photos/{fileId} {
      allow read: if
        firestore.exists(/databases/(default)/documents/users/$(userId)/friends/$(request.auth.id))
    }
  }
}

在您创建并保存首个使用这些 Cloud Firestore 函数的 Cloud Storage 安全规则后,系统会通过 Firebase 控制台或 Firebase CLI 提示您启用相应权限以将这两款产品关联起来。

您可以通过移除相应 IAM 角色来停用该功能,如管理和部署 Firebase 安全规则中所述。

验证数据

面向 Cloud Storage 的 Firebase 安全规则也可用于数据验证,包括验证文件名和路径以及文件元数据属性(例如 contentTypesize)。

service firebase.storage {
  match /b/{bucket}/o {
    match /images/{imageId} {
      // Only allow uploads of any image file that's less than 5MB
      allow write: if request.resource.size < 5 * 1024 * 1024
                   && request.resource.contentType.matches('image/.*');
    }
  }
}

自定义函数

随着 Firebase 安全规则变得越来越复杂,您可能需要将条件集封装在函数中,以便在规则集中重复使用。安全规则支持自定义函数。自定义函数的语法有点类似于 JavaScript,但 Firebase 安全规则函数是用网域特定语言编写的,该语言具有以下一些重要限制:

  • 函数只能包含一个 return 语句,不能包含任何额外的逻辑。例如,它们无法执行循环或调用外部服务。
  • 函数可以自动访问其所在范围内的函数和变量。例如,在 service firebase.storage 范围内定义的函数可以访问 resource 变量以及(仅限于 Cloud Firestore)get()exists() 等内置函数。
  • 函数可以调用其他函数,但无法递归。调用堆栈总深度不得超过 10。
  • 在版本 rules2 中,函数可以使用 let 关键字定义变量。函数可包含任意数量的 let 绑定,但必须以 return 语句结尾。

函数是用 function 关键字定义的,可以接受零个或零个以上的参数。例如,您可能想要将上述示例中使用的两种条件组合成一个函数:

service firebase.storage {
  match /b/{bucket}/o {
    // True if the user is signed in or the requested data is 'public'
    function signedInOrPublic() {
      return request.auth.uid != null || resource.data.visibility == 'public';
    }
    match /images/{imageId} {
      allow read, write: if signedInOrPublic();
    }
    match /mp3s/{mp3Ids} {
      allow read: if signedInOrPublic();
    }
  }
}

在 Firebase 安全规则中使用函数可以在规则变得越来越复杂时使其更易于维护。

后续步骤

在讨论条件之后,您将对规则有更深入的了解,而且可以:

了解如何处理核心使用场景以及了解开发、测试和部署规则的工作流程