安全查詢數據

本頁面以建立安全規則編寫安全規則條件中的概念為基礎,解釋 Cloud Firestore 安全性規則如何與查詢互動。它仔細研究了安全規則如何影響您可以編寫的查詢,並描述如何確保您的查詢使用與安全規則相同的限制。本頁也介紹如何撰寫安全規則以根據limitorderBy等查詢屬性允許或拒絕查詢。

規則不是過濾器

在撰寫查詢來檢索文件時,請記住安全規則不是過濾器——查詢要么全有,要么全無。為了節省您的時間和資源,Cloud Firestore 會根據其潛在結果集而不是所有文件的實際欄位值來評估查詢。如果查詢可能傳回客戶端無權讀取的文檔,則整個請求將會失敗。

查詢和安全規則

正如下面的範例所示,您必須編寫查詢以適應安全規則的約束。

基於auth.uid保護和查詢文檔

以下範例示範如何撰寫查詢來擷取受安全規則保護的文件。考慮一個包含story文檔集合的資料庫:

/故事/{storyid}

{
  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字段設置為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查詢( orinarray-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變數包含查詢的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
    }
  }
}

下一步