Cloud Firestore 安全規則的編寫條件

本指南以構建安全規則指南為基礎,展示如何向 Cloud Firestore 安全規則添加條件。如果您不熟悉 Cloud Firestore 安全規則的基礎知識,請參閱入門指南。

Cloud Firestore 安全規則的主要構建塊是條件。條件是一個布爾表達式,用於確定是否應允許或拒絕特定操作。使用安全規則編寫檢查用戶身份驗證、驗證傳入數據甚至訪問數據庫其他部分的條件。

驗證

最常見的安全規則模式之一是根據用戶的身份驗證狀態控制訪問。例如,您的應用可能希望僅允許登錄用戶寫入數據:

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to access documents in the "cities" collection
    // only if they are authenticated.
    match /cities/{city} {
      allow read, write: if request.auth != null;
    }
  }
}

另一種常見模式是確保用戶只能讀取和寫入自己的數據:

service cloud.firestore {
  match /databases/{database}/documents {
    // Make sure the uid of the requesting user matches name of the user
    // document. The wildcard expression {userId} makes the userId variable
    // available in rules.
    match /users/{userId} {
      allow read, update, delete: if request.auth != null && request.auth.uid == userId;
      allow create: if request.auth != null;
    }
  }
}

如果您的應用使用 Firebase Authentication 或Google Cloud Identity Platform ,則request.auth變量包含請求數據的客戶端的身份驗證信息。有關request.auth的更多信息,請參閱參考文檔

數據驗證

許多應用程序將訪問控制信息存儲為數據庫中文檔的字段。 Cloud Firestore 安全規則可以根據文檔數據動態允許或拒絕訪問:

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to read data if the document has the 'visibility'
    // field set to 'public'
    match /cities/{city} {
      allow read: if resource.data.visibility == 'public';
    }
  }
}

resource變量引用所請求的文檔, resource.data是存儲在文檔中的所有字段和值的映射。有關resource變量的更多信息,請參閱參考文檔

寫入數據時,您可能希望將傳入數據與現有數據進行比較。在這種情況下,如果您的規則集允許掛起寫入,則request.resource變量包含文檔的未來狀態。對於僅修改文檔字段子集的update操作, request.resource變量將包含操作後的待處理文檔狀態。您可以檢查request.resource中的字段值以防止不必要或不一致的數據更新:

service cloud.firestore {
  match /databases/{database}/documents {
    // Make sure all cities have a positive population and
    // the name is not changed
    match /cities/{city} {
      allow update: if request.resource.data.population > 0
                    && request.resource.data.name == resource.data.name;
    }
  }
}

訪問其他文檔

使用get()exists()函數,您的安全規則可以根據數據庫中的其他文檔評估傳入請求。 get()exists()函數都需要完全指定的文檔路徑。當使用變量為get()exists()構造路徑時,需要使用$(variable)語法顯式轉義變量。

在下面的示例中, database變量由 match 語句match /databases/{database}/documents捕獲並用於形成路徑:

service cloud.firestore {
  match /databases/{database}/documents {
    match /cities/{city} {
      // Make sure a 'users' document exists for the requesting user before
      // allowing any writes to the 'cities' collection
      allow create: if request.auth != null && exists(/databases/$(database)/documents/users/$(request.auth.uid))

      // Allow the user to delete cities if their user document has the
      // 'admin' field set to 'true'
      allow delete: if request.auth != null && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true
    }
  }
}

對於寫入,您可以使用getAfter()函數在事務或批量寫入完成之後但在事務或批量提交之前訪問文檔的狀態。與get()一樣, getAfter()函數採用完全指定的文檔路徑。您可以使用getAfter()定義必須作為事務或批處理一起發生的寫入集。

訪問通話限制

每個規則集評估的文檔訪問調用有限制:

  • 10 用於單文檔請求和查詢請求。
  • 20 用於多文檔讀取、事務和批量寫入。之前的 10 個限制也適用於每個操作。

    例如,假設您創建一個包含 3 個寫入操作的批量寫入請求,並且您的安全規則使用 2 個文檔訪問調用來驗證每個寫入。在這種情況下,每個寫入使用其 10 個訪問調用中的 2 個,而批量寫入請求使用其 20 個訪問調用中的 6 個。

超過任一限制都會導致權限被拒絕錯誤。某些文檔訪問調用可能會被緩存,並且緩存的調用不計入限制。

有關這些限制如何影響事務和批量寫入的詳細說明,請參閱保護原子操作指南。

訪問通話和定價

使用這些函數會在數據庫中執行讀取操作,這意味著即使您的規則拒絕該請求,您也需要為讀取文檔付費。請參閱Cloud Firestore 定價了解更多具體的計費信息。

自定義功能

隨著安全規則變得越來越複雜,您可能希望將條件集包裝在可以在規則集中重複使用的函數中。安全規則支持自定義功能。自定義函數的語法有點像 JavaScript,但安全規則函數是用特定於域的語言編寫的,具有一些重要的限制:

  • 函數只能包含單個return語句。它們不能包含任何額外的邏輯。例如,它們無法執行循環或調用外部服務。
  • 函數可以自動從定義的範圍內訪問函數和變量。例如,在service cloud.firestore範圍內定義的函數可以訪問resource變量和內置函數,例如get()exists()
  • 函數可以調用其他函數,但不能遞歸。總調用堆棧深度限制為 10。
  • 在規則版本v2中,函數可以使用let關鍵字定義變量。函數最多可以有 10 個 let 綁定,但必須以 return 語句結束。

函數是使用function關鍵字定義的,並採用零個或多個參數。例如,您可能希望將上面示例中使用的兩種類型的條件組合成一個函數:

service cloud.firestore {
  match /databases/{database}/documents {
    // 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 /cities/{city} {
      allow read, write: if signedInOrPublic();
    }

    match /users/{user} {
      allow read, write: if signedInOrPublic();
    }
  }
}

隨著規則復雜性的增加,在安全規則中使用函數可以使它們更易於維護。

規則不是過濾器

一旦您保護數據並開始編寫查詢,請記住安全規則不是過濾器。您無法為集合中的所有文檔編寫查詢並期望 Cloud Firestore 僅返回當前客戶端有權訪問的文檔。

例如,採用以下安全規則:

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to read data if the document has the 'visibility'
    // field set to 'public'
    match /cities/{city} {
      allow read: if resource.data.visibility == 'public';
    }
  }
}

拒絕:此規則拒絕以下查詢,因為結果集可能包含visibilitypublic文檔:

網絡
db.collection("cities").get()
    .then(function(querySnapshot) {
        querySnapshot.forEach(function(doc) {
            console.log(doc.id, " => ", doc.data());
    });
});

允許:此規則允許以下查詢,因為where("visibility", "==", "public")子句保證結果集滿足規則的條件:

網絡
db.collection("cities").where("visibility", "==", "public").get()
    .then(function(querySnapshot) {
        querySnapshot.forEach(function(doc) {
            console.log(doc.id, " => ", doc.data());
        });
    });

Cloud Firestore 安全規則會根據每個查詢的潛在結果評估每個查詢,如果它可能返回客戶端無權讀取的文檔,則該請求將失敗。查詢必須遵循安全規則設置的約束。有關安全規則和查詢的更多信息,請參閱安全查詢數據

下一步