获取我们在 Firebase 峰会上发布的所有信息,了解 Firebase 可如何帮助您加快应用开发速度并满怀信心地运行应用。了解详情

安全查询数据

使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。

本页面以构建安全规则编写安全规则条件中的概念为基础,解释 Cloud Firestore 安全规则如何与查询交互。它仔细研究了安全规则如何影响您可以编写的查询,并描述了如何确保您的查询使用与安全规则相同的约束。此页面还介绍了如何编写安全规则以允许或拒绝基于limitorderBy等查询属性的查询。

规则不是过滤器

在编写检索文档的查询时,请记住安全规则不是过滤器——查询是全有或全无。为了节省您的时间和资源,Cloud Firestore 会根据查询的潜在结果集而不是所有文档的实际字段值来评估查询。如果查询可能会返回客户端无权读取的文档,则整个请求将失败。

查询和安全规则

如以下示例所示,您必须编写查询以符合安全规则的约束。

基于auth.uid的安全和查询文档

以下示例演示如何编写查询来检索受安全规则保护的文档。考虑一个包含story文档集合的数据库:

/故事/{故事编号}

{
  title: "A Great Story",
  content: "Once upon a time...",
  author: "some_auth_id",
  published: false
}

除了titlecontent字段之外,每个文档还存储authorpublished字段以用于访问控制。这些示例假设应用程序使用Firebase 身份验证author字段设置为创建文档的用户的 UID。 Firebase 身份验证还会在安全规则中填充request.auth变量。

以下安全规则使用request.authresource.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字段设置为truestory文档。

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 。因此,该查询满足安全规则并被允许读取数据。

inarray-contains-any查询

在根据规则集评估inarray-contains-any查询子句时,Cloud Firestore 会分别评估每个比较值。每个比较值都必须满足安全规则约束。例如,对于以下规则:

match /mydocuments/{doc} {
  allow read: if resource.data.x > 5;
}

无效:查询不保证所有潜在文档的x > 5

// This query will fail
db.collection("mydocuments").where("x", "in", [1, 3, 6, 42, 99]).get()

有效:查询保证所有潜在文档的x > 5

db.collection("mydocuments").where("x", "in", [6, 42, 99, 105, 200]).get()

评估查询约束

您的安全规则还可以根据其约束接受或拒绝查询。 request.query变量包含查询的limitoffsetorderBy属性。例如,您的安全规则可以拒绝任何未将检索的最大文档数限制在特定范围内的查询:

allow list: if request.query.limit <= 10;

以下规则集演示了如何编写评估对查询施加的约束的安全规则。此示例通过以下更改扩展了以前的stories规则集:

  • 规则集将读取规则分为getlist规则。
  • 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 的所有集合的集合组中检索结果。本节介绍如何使用安全规则保护您的集合组查询。

基于集合组保护和查询文档

在您的安全规则中,您必须通过为集合组编写规则来明确允许集合组查询:

  1. 确保rules_version = '2';是规则集的第一行。集合组查询需要安全规则版本 2 的新递归通配符{name=**}行为。
  2. 使用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收集组编写读取或列表规则来允许此查询:

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}

基于字段的安全集合组查询

与单个集合查询一样,集合组查询也必须满足您的安全规则设置的约束。例如,我们可以向每个论坛帖子添加一个published的字段,就像我们在上面的stories示例中所做的那样:

/forums/{forumid}/posts/{postid}

{
  author: "some_auth_id",
  authorname: "some_username",
  content: "I just read a great story.",
  published: false
}

然后我们可以根据published状态和帖子authorposts收集组编写规则:

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
    }
  }
}

下一步