本页面以设计安全规则结构和编写安全规则的条件中的概念为基础,介绍 Cloud Firestore 安全规则与查询之间的相互作用。其中将更详细地介绍安全规则如何影响您可以编写的查询,以及如何确保您的查询与安全规则使用相同的限制条件。本页面还介绍了如何编写安全规则,以根据查询的属性(如 limit
和 orderBy
)允许或拒绝查询。
规则并非过滤条件
在编写查询以检索文档时,请务必留意,安全规则不是过滤条件 - 查询结果要么包含全部,要么什么也没有。为了节省时间和资源,Cloud Firestore 会针对查询的可能结果集(而不是您所有文档的实际字段值)来对其进行评估。如果查询可能会返回客户端无权读取的文档,则整个请求将会失败。
查询和安全规则
如下例所示,您编写的查询必须符合您的安全规则的限制条件。
根据 auth.uid
保护和查询文档
以下示例演示了如何编写查询以检索受安全规则保护的文档。假设某数据库包含了一个 story
文档的集合:
/stories/{storyid}
{
title: "A Great Story",
content: "Once upon a time...",
author: "some_auth_id",
published: false
}
除了 title
和 content
字段之外,每个文档还存储了用于控制访问的 author
和 published
字段。这些示例假定应用使用 Firebase Authentication 将 author
字段设置为创建文档的用户的 UID。Firebase Authentication 还会填充安全规则中的 request.auth
变量。
以下安全规则使用 request.auth
和 resource.data
变量限定每个 story
的读写权限仅归其作者所有:
service cloud.firestore {
match /databases/{database}/documents {
match /stories/{storyid} {
// Only the authenticated user who authored the document can read or write
allow read, write: if request.auth != null && request.auth.uid == resource.data.author;
}
}
}
假设您的应用包含一个页面,其中向用户列出他们所创建的 story
文档。您也许会认为,可以使用以下查询来填充此页面。但是,由于没有包含与您的安全规则相同的限制条件,此查询将会失败:
无效:查询限制条件与安全规则限制条件不匹配
// This query will fail
db.collection("stories").get()
即使当前用户实际上是所有 story
文档的作者,查询也会失败。造成这种情况的原因是,当 Cloud Firestore 应用您的安全规则时,它会针对可能的结果集评估查询,而不是针对数据库中文档的实际属性进行评估。如果查询可能包括违反安全规则的文档,则查询将会失败。
相反,由于在 author
字段中包含了与安全规则相同的限制条件,因此以下查询将会成功:
有效:查询和安全规则的限制条件匹配
var user = firebase.auth().currentUser;
db.collection("stories").where("author", "==", user.uid).get()
根据字段保护和查询文档
为了进一步演示查询和规则之间的相互作用,下面的安全规则扩展了 stories
集合的读取权限,以允许任意用户读取 published
字段设置为 true
的 story
文档。
service cloud.firestore {
match /databases/{database}/documents {
match /stories/{storyid} {
// Anyone can read a published story; only story authors can read unpublished stories
allow read: if resource.data.published == true || (request.auth != null && request.auth.uid == resource.data.author);
// Only story authors can write
allow write: if request.auth != null && request.auth.uid == resource.data.author;
}
}
}
针对已发布页面的查询必须包含与安全规则相同的限制条件:
db.collection("stories").where("published", "==", true).get()
对于任何结果,查询限制条件 .where("published", "==", true)
都保证 resource.data.published
为 true
。因此,该查询满足安全规则,可用于读取数据。
OR
查询
根据规则集评估逻辑 OR
查询(or
、in
或 array-contains-any
)时,Cloud Firestore 会分别评估每个比较值。每个比较值都必须满足安全规则限制条件。例如,对于以下规则:
match /mydocuments/{doc} {
allow read: if resource.data.x > 5;
}
无效:查询并不保证所有可能的文档都满足 x > 5
// These queries will fail
query(db.collection("mydocuments"),
or(where("x", "==", 1),
where("x", "==", 6)
)
)
query(db.collection("mydocuments"),
where("x", "in", [1, 3, 6, 42, 99])
)
有效:对于所有可能的文档,查询都保证 x > 5
query(db.collection("mydocuments"),
or(where("x", "==", 6),
where("x", "==", 42)
)
)
query(db.collection("mydocuments"),
where("x", "in", [6, 42, 99, 105, 200])
)
评估查询的限制条件
您的安全规则也可以根据查询的限制条件接受或拒绝查询。
request.query
变量包含查询的 limit
、offset
、orderBy
属性。例如,如果查询没有将检索到的文档数量上限限制为特定范围,那么您的安全规则可以拒绝该查询:
allow list: if request.query.limit <= 10;
以下规则集演示了如何编写会评估查询的限制条件的安全规则。此示例扩展了上述 stories
规则集,并进行了以下更改:
- 该规则集将读取规则拆分成了
get
规则和list
规则。 get
规则将针对单个文档的检索限定为公开文档或用户编写的文档。list
规则会应用与get
规则相同的限制条件,但应用对象为查询。它还会检查查询的限制,并拒绝没有设置数量限制或数量限制大于 10 的查询。- 该规则集定义了一个
authorOrPublished()
函数以避免代码重复。
service cloud.firestore {
match /databases/{database}/documents {
match /stories/{storyid} {
// Returns `true` if the requested story is 'published'
// or the user authored the story
function authorOrPublished() {
return resource.data.published == true || request.auth.uid == resource.data.author;
}
// Deny any query not limited to 10 or fewer documents
// Anyone can query published stories
// Authors can query their unpublished stories
allow list: if request.query.limit <= 10 &&
authorOrPublished();
// Anyone can retrieve a published story
// Only a story's author can retrieve an unpublished story
allow get: if authorOrPublished();
// Only a story's author can write to a story
allow write: if request.auth.uid == resource.data.author;
}
}
}
集合组查询和安全规则
默认情况下,查询的范围限定为单个集合,并且只从该集合中检索结果。使用集合组查询,您可以从包含具有相同 ID 的所有集合的集合组中检索结果。本部分介绍如何使用安全规则来保护集合组查询。
根据集合组保护和查询文档
在安全规则中,您必须通过为集合组编写规则来明确允许集合组查询:
- 确保
rules_version = '2';
是您的规则集的第一行。集合组查询需要安全规则版本 2 的新递归通配符{name=**}
行为。 - 使用
match /{path=**}/[COLLECTION_ID]/{doc}
为您的集合组编写规则。
例如,假设某个论坛被整理成包含 posts
子集合的 forum
文档:
/forums/{forumid}/posts/{postid}
{
author: "some_auth_id",
authorname: "some_username",
content: "I just read a great story.",
}
在此应用中,我们使帖子可由其所有者修改,并且可由经过身份验证的用户阅读:
service cloud.firestore {
match /databases/{database}/documents {
match /forums/{forumid}/posts/{post} {
// Only authenticated users can read
allow read: if request.auth != null;
// Only the post author can write
allow write: if request.auth != null && request.auth.uid == resource.data.author;
}
}
}
任何经过身份验证的用户都可以检索任何单个论坛的帖子:
db.collection("forums/technology/posts").get()
但是,如果您想为当前用户展示他们在所有论坛中的帖子,该怎么办?
您可以使用集合组查询从所有 posts
集合中检索结果:
var user = firebase.auth().currentUser;
db.collectionGroup("posts").where("author", "==", user.uid).get()
在安全规则中,您必须通过为 posts
集合组编写 read 或 list 规则来允许此查询:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Authenticated users can query the posts collection group
// Applies to collection queries, collection group queries, and
// single document retrievals
match /{path=**}/posts/{post} {
allow read: if request.auth != null;
}
match /forums/{forumid}/posts/{postid} {
// Only a post's author can write to a post
allow write: if request.auth != null && request.auth.uid == resource.data.author;
}
}
}
但请注意,这些规则将应用于所有 ID 为 posts
的集合,而无论层次结构如何。例如,这些规则适用于以下所有 posts
集合:
/posts/{postid}
/forums/{forumid}/posts/{postid}
/forums/{forumid}/subforum/{subforumid}/posts/{postid}
根据字段保护集合组查询
与单个集合查询一样,集合组查询也必须满足安全规则设置的限制条件。例如,我们可以像在上面的 stories
示例中一样在每个论坛帖子中添加 published
字段:
/forums/{forumid}/posts/{postid}
{
author: "some_auth_id",
authorname: "some_username",
content: "I just read a great story.",
published: false
}
然后,我们可以根据 published
状态和帖子 author
为 posts
集合组编写规则:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Returns `true` if the requested post is 'published'
// or the user authored the post
function authorOrPublished() {
return resource.data.published == true || request.auth.uid == resource.data.author;
}
match /{path=**}/posts/{post} {
// Anyone can query published posts
// Authors can query their unpublished posts
allow list: if authorOrPublished();
// Anyone can retrieve a published post
// Authors can retrieve an unpublished post
allow get: if authorOrPublished();
}
match /forums/{forumid}/posts/{postid} {
// Only a post's author can write to a post
allow write: if request.auth.uid == resource.data.author;
}
}
}
借助这些规则,Web、Apple、Android 客户端可以执行以下查询:
任何人都可以在论坛中检索已发布的贴子:
db.collection("forums/technology/posts").where('published', '==', true).get()
任何人都可以在所有论坛中检索某个作者发布的帖子:
db.collectionGroup("posts").where("author", "==", "some_auth_id").where('published', '==', true).get()
作者可以在所有论坛中检索其所有已发布和已取消发布的帖子:
var user = firebase.auth().currentUser; db.collectionGroup("posts").where("author", "==", user.uid).get()
根据集合组和文档路径保护和查询文档
在某些情况下,您可能希望根据文档路径限制集合组查询。如需创建这些限制,您可以使用相同的技术基于某个字段来保护和查询文档。
假设有一个应用,可跟踪每个用户在多个股票和加密货币交易所之间的交易:
/users/{userid}/exchange/{exchangeid}/transactions/{transaction}
{
amount: 100,
exchange: 'some_exchange_name',
timestamp: April 1, 2019 at 12:00:00 PM UTC-7,
user: "some_auth_id",
}
请注意 user
字段。即使我们可以通过文档路径知道哪个用户拥有 transaction
文档,我们也会在每个 transaction
文档中复制此信息,因为借助此信息可以执行以下两项操作:
编写集合组查询,使其查询范围仅限于在其文档路径中包含特定
/users/{userid}
的文档。例如:var user = firebase.auth().currentUser; // Return current user's last five transactions across all exchanges db.collectionGroup("transactions").where("user", "==", user).orderBy('timestamp').limit(5)
对针对
transactions
集合组执行的所有查询强制执行此限制,以使一个用户无法检索另一个用户的transaction
文档。
我们在安全规则中强制执行此限制,并为 user
字段添加了数据验证:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{path=**}/transactions/{transaction} {
// Authenticated users can retrieve only their own transactions
allow read: if resource.data.user == request.auth.uid;
}
match /users/{userid}/exchange/{exchangeid}/transactions/{transaction} {
// Authenticated users can write to their own transactions subcollections
// Writes must populate the user field with the correct auth id
allow write: if userid == request.auth.uid && request.data.user == request.auth.uid
}
}
}
后续步骤
- 如需更详细的示例来了解基于角色的访问控制,请参阅帮助用户和用户组确保数据访问的安全性。
- 阅读安全规则参考文档。