本指南以学习 Firebase 安全规则语言的核心语法指南为基础,介绍如何在面向 Cloud Storage 的 Firebase 安全规则中添加条件。
Cloud Storage 安全规则的主要构成要素是条件。条件是一个布尔表达式,用于确定应该允许还是拒绝执行特定操作。对于基本规则,最好使用 true
和 false
字面量作为条件。不过,“面向 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; }
群组私有
另一个同样常见的使用场景是为群组提供对象权限,例如允许多名团队成员协同撰写或修改某个共享的文档。下列几种方法可以实现这个目的:
将这些数据存储在令牌或文件元数据中后,就可以在规则中引用这些数据:
// 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 对象中提供的元数据键值对。可以在 read
或 write
请求中检查这些属性,以确保数据完整性。
在 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
包含上述除 generation
、metageneration
、etag
、timeCreated
和 updated
外的所有属性。
使用 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 安全规则也可用于数据验证,包括验证文件名和路径以及文件元数据属性(例如 contentType
和 size
)。
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 安全规则中使用函数可以在规则变得越来越复杂时使其更易于维护。
后续步骤
在讨论条件之后,您将对规则有更深入的了解,而且可以:
了解如何处理核心使用场景以及了解开发、测试和部署规则的工作流程:
- 编写用于解决常见场景的规则。
- 如需进一步熟悉,请查看必须发现并避免不安全规则的情况。
- 使用 Cloud Storage 模拟器和专用安全规则测试库测试规则。
- 查看可用于部署规则的方法。