安全性可能是應用程序開發難題中最複雜的部分之一。在大多數應用程序中,開發人員必須構建並運行一個服務器來處理身份驗證(用戶是誰)和授權(用戶可以做什麼)。
Firebase 安全規則刪除了中間(服務器)層,並允許您為直接連接到您的數據的客戶端指定基於路徑的權限。使用本指南了解有關如何將規則應用於傳入請求的更多信息。
選擇一個產品以了解有關其規則的更多信息。
雲端 Firestore
基本結構
Cloud Firestore 和 Cloud Storage 中的 Firebase 安全規則使用以下結構和語法:
service <<name>> {
// Match the resource path.
match <<path>> {
// Allow the request if the following conditions are true.
allow <<methods>> : if <<condition>>
}
}
在構建規則時,了解以下關鍵概念很重要:
- Request:在
allow
語句中調用的一個或多個方法。這些是您允許運行的方法。標準方法是:get
、list
、create
、update
和delete
。read
和write
便利方法支持對指定數據庫或存儲路徑進行廣泛的讀取和寫入訪問。 - 路徑:數據庫或存儲位置,表示為 URI 路徑。
- 規則:
allow
語句,其中包含一個條件,如果它的計算結果為真,則允許請求。
安全規則版本 2
自 2019 年 5 月起,Firebase 安全規則的第 2 版現已可用。規則的版本 2 更改了遞歸通配符{name=**}
的行為。如果您計劃使用集合組查詢,則必須使用版本 2。您必須通過設置rules_version = '2';
安全規則的第一行:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
匹配路徑
所有匹配語句都應指向文檔,而不是集合。 match 語句可以指向特定文檔,如match /cities/SF
或使用通配符指向指定路徑中的任何文檔,如match /cities/{city}
。
在上面的示例中,匹配語句使用{city}
通配符語法。這意味著該規則適用於cities
集合中的任何文檔,例如/cities/SF
或/cities/NYC
。當評估 match 語句中的allow
表達式時, city
變量將解析為城市文檔名稱,例如SF
或NYC
。
匹配子集合
Cloud Firestore 中的數據被組織成文檔集合,每個文檔都可以通過子集合擴展層次結構。了解安全規則如何與分層數據交互很重要。
考慮cities
集合中的每個文檔都包含一個landmarks
子集合的情況。安全規則僅適用於匹配的路徑,因此在cities
集合上定義的訪問控制不適用於landmarks
子集合。相反,編寫明確的規則來控制對子集合的訪問:
service cloud.firestore {
match /databases/{database}/documents {
match /cities/{city} {
allow read, write: if <condition>;
// Explicitly define rules for the 'landmarks' subcollection
match /landmarks/{landmark} {
allow read, write: if <condition>;
}
}
}
}
嵌套match
語句時,內層match
語句的路徑總是相對於外層match
語句的路徑。因此,以下規則集是等效的:
service cloud.firestore {
match /databases/{database}/documents {
match /cities/{city} {
match /landmarks/{landmark} {
allow read, write: if <condition>;
}
}
}
}
service cloud.firestore {
match /databases/{database}/documents {
match /cities/{city}/landmarks/{landmark} {
allow read, write: if <condition>;
}
}
}
遞歸通配符
如果您希望規則適用於任意深度的層次結構,請使用遞歸通配符語法{name=**}
:
service cloud.firestore {
match /databases/{database}/documents {
// Matches any document in the cities collection as well as any document
// in a subcollection.
match /cities/{document=**} {
allow read, write: if <condition>;
}
}
}
使用遞歸通配符語法時,通配符變量將包含整個匹配路徑段,即使文檔位於深度嵌套的子集合中。例如,上面列出的規則將匹配位於/cities/SF/landmarks/coit_tower
文檔, document
變量的值將為SF/landmarks/coit_tower
。
但是請注意,遞歸通配符的行為取決於規則版本。
版本 1
默認情況下,安全規則使用版本 1。在版本 1 中,遞歸通配符匹配一個或多個路徑項。它們不匹配空路徑,因此match /cities/{city}/{document=**}
匹配子集合中的文檔但不匹配cities
集合中的文檔,而match /cities/{document=**}
匹配子集合中的兩個文檔cities
集合和子集合。
遞歸通配符必須出現在匹配語句的末尾。
版本 2
在安全規則的版本 2 中,遞歸通配符匹配零個或多個路徑項。 match/cities/{city}/{document=**}
匹配任何子集合中的文檔以及cities
集合中的文檔。
您必須通過添加rules_version = '2';
在您的安全規則的頂部:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Matches any document in the cities collection as well as any document
// in a subcollection.
match /cities/{city}/{document=**} {
allow read, write: if <condition>;
}
}
}
每個匹配語句最多可以有一個遞歸通配符,但在版本 2 中,您可以將此通配符放在匹配語句中的任何位置。例如:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Matches any document in the songs collection group
match /{path=**}/songs/{song} {
allow read, write: if <condition>;
}
}
}
如果您使用集合組查詢,則必須使用版本 2,請參閱保護集合組查詢。
重疊匹配語句
一個文檔可能匹配多個match
語句。在多個allow
表達式匹配請求的情況下,如果任何條件為true
,則允許訪問:
service cloud.firestore {
match /databases/{database}/documents {
// Matches any document in the 'cities' collection.
match /cities/{city} {
allow read, write: if false;
}
// Matches any document in the 'cities' collection or subcollections.
match /cities/{document=**} {
allow read, write: if true;
}
}
}
在上面的示例中,將允許對cities
集合的所有讀寫操作,因為第二條規則始終為true
,即使第一條規則始終為false
。
安全規則限制
在使用安全規則時,請注意以下限制:
限制 | 細節 |
---|---|
每個請求的exists() 、 get() 和getAfter() 調用的最大次數 |
超過任一限制都會導致權限被拒絕錯誤。 某些文檔訪問調用可能會被緩存,緩存的調用不計入限制。 |
最大嵌套match 語句深度 | 10 |
一組嵌套match 語句中允許的最大路徑長度(以路徑段為單位) | 100 |
一組嵌套match 語句中允許的最大路徑捕獲變量數 | 20 |
最大函數調用深度 | 20 |
函數參數的最大數量 | 7 |
每個函數的最大let 變量綁定數 | 10 |
遞歸或循環函數調用的最大次數 | 0(不允許) |
每個請求評估的最大表達式數 | 1,000 |
規則集的最大大小 | 規則集必須遵守兩個大小限制:
|
雲儲存
基本結構
Cloud Firestore 和 Cloud Storage 中的 Firebase 安全規則使用以下結構和語法:
service <<name>> {
// Match the resource path.
match <<path>> {
// Allow the request if the following conditions are true.
allow <<methods>> : if <<condition>>
}
}
在構建規則時,了解以下關鍵概念很重要:
- Request:在
allow
語句中調用的一個或多個方法。這些是您允許運行的方法。標準方法是:get
、list
、create
、update
和delete
。read
和write
便利方法支持對指定數據庫或存儲路徑進行廣泛的讀取和寫入訪問。 - 路徑:數據庫或存儲位置,表示為 URI 路徑。
- 規則:
allow
語句,其中包含一個條件,如果它的計算結果為真,則允許請求。
匹配路徑
Cloud Storage 安全規則match
用於訪問 Cloud Storage 中文件的文件路徑。規則可以match
精確路徑或通配符路徑,規則也可以嵌套。如果沒有匹配規則允許請求方法,或者條件評估為false
,則請求被拒絕。
完全匹配
// Exact match for "images/profilePhoto.png" match /images/profilePhoto.png { allow write: if <condition>; } // Exact match for "images/croppedProfilePhoto.png" match /images/croppedProfilePhoto.png { allow write: if <other_condition>; }
嵌套匹配
// Partial match for files that start with "images" match /images { // Exact match for "images/profilePhoto.png" match /profilePhoto.png { allow write: if <condition>; } // Exact match for "images/croppedProfilePhoto.png" match /croppedProfilePhoto.png { allow write: if <other_condition>; } }
通配符比賽
規則也可用於使用通配符來match
模式。通配符是一個命名變量,表示單個字符串(例如profilePhoto.png
或多個路徑段(例如images/profilePhoto.png
。
通過在通配符名稱周圍添加花括號來創建通配符,例如{string}
。可以通過將=**
添加到通配符名稱來聲明多段通配符,例如{path=**}
:
// Partial match for files that start with "images" match /images { // Exact match for "images/*" // e.g. images/profilePhoto.png is matched match /{imageId} { // This rule only matches a single path segment (*) // imageId is a string that contains the specific segment matched allow read: if <condition>; } // Exact match for "images/**" // e.g. images/users/user:12345/profilePhoto.png is matched // images/profilePhoto.png is also matched! match /{allImages=**} { // This rule matches one or more path segments (**) // allImages is a path that contains all segments matched allow read: if <other_condition>; } }
如果多個規則匹配一個文件,則結果是所有規則評估結果的OR
。也就是說,如果文件匹配的任何規則評估為true
,則結果為true
。
在上面的規則中,如果condition
或other_condition
計算結果為真,則可以讀取文件“images/profilePhoto.png”,而文件“images/users/user:12345/profilePhoto.png”僅受other_condition
的結果影響.
可以從match
中引用通配符變量提供文件名或路徑授權:
// Another way to restrict the name of a file match /images/{imageId} { allow read: if imageId == "profilePhoto.png"; }
雲存儲安全規則不會級聯,只有當請求路徑與指定規則的路徑匹配時才會評估規則。
請求評估
使用發送到 Cloud Storage 的request
評估上傳、下載、元數據更改和刪除。 request
變量包含執行請求的文件路徑、收到請求的時間以及新的resource
值(如果請求是寫入)。還包括 HTTP 標頭和身份驗證狀態。
request
對像還包含用戶的唯一 ID 和request.auth
對像中的 Firebase 身份驗證有效負載,這將在文檔的身份驗證部分中進一步解釋。
request
對像中的完整屬性列表如下:
財產 | 類型 | 描述 |
---|---|---|
auth | 地圖<字符串,字符串> | 當用戶登錄時,提供用戶的唯一 ID uid 和token ,即 Firebase 身份驗證 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
除外。
安全規則限制
在使用安全規則時,請注意以下限制:
限制 | 細節 |
---|---|
每個請求的最大firestore.exists() 和firestore.get() 調用次數 | 2 用於單文檔請求和查詢請求。 超過此限制會導致權限被拒絕錯誤。 對相同文檔的訪問調用可能會被緩存,緩存的調用不計入限制。 |
完整示例
綜上所述,您可以為圖像存儲解決方案創建一個完整的規則示例:
service firebase.storage { match /b/{bucket}/o { match /images { // Cascade read to any image type at any path match /{allImages=**} { allow read; } // Allow write files to the path "images/*", subject to the constraints: // 1) File is less than 5MB // 2) Content type is an image // 3) Uploaded content type matches existing content type // 4) File name (stored in imageId wildcard variable) is less than 32 characters match /{imageId} { allow write: if request.resource.size < 5 * 1024 * 1024 && request.resource.contentType.matches('image/.*') && request.resource.contentType == resource.contentType && imageId.size() < 32 } } } }
實時數據庫
基本結構
在實時數據庫中,Firebase 安全規則由 JSON 文檔中包含的類似 JavaScript 的表達式組成。
他們使用以下語法:
{
"rules": {
"<<path>>": {
// Allow the request if the condition for each method is true.
".read": <<condition>>,
".write": <<condition>>,
".validate": <<condition>>
}
}
}
該規則包含三個基本要素:
- 路徑:數據庫位置。這反映了數據庫的 JSON 結構。
- 請求:這些是規則用於授予訪問權限的方法。
read
和write
規則授予廣泛的讀取和寫入訪問權限,而validate
規則充當輔助驗證以根據傳入或現有數據授予訪問權限。 - 條件:如果計算結果為真,則允許請求的條件。
規則如何應用於路徑
在實時數據庫中,規則以原子方式應用,這意味著更高級別父節點的規則會覆蓋更細粒度的子節點的規則,而更深層次節點的規則無法授予對父路徑的訪問權限。如果您已經為其中一個父路徑授予訪問權限,則無法優化或撤銷對數據庫結構中更深路徑的訪問權限。
考慮以下規則:
{ "rules": { "foo": { // allows read to /foo/* ".read": "data.child('baz').val() === true", "bar": { // ignored, since read was allowed already ".read": false } } } }
此安全結構允許在/foo/
包含值為true
子baz
時讀取/bar/
。 /foo/bar/
下的".read": false
規則在這裡無效,因為訪問不能被子路徑撤銷。
雖然它可能看起來並不直觀,但它是規則語言的強大部分,允許以最小的努力實現非常複雜的訪問權限。這對於基於用戶的安全性特別有用。
但是, .validate
規則不會級聯。必須滿足層次結構的所有級別的所有驗證規則,才能允許寫入。
此外,由於規則不適用於父路徑,如果在請求的位置或授予訪問權限的父位置沒有規則,則讀取或寫入操作將失敗。即使每個受影響的子路徑都可以訪問,在父位置的讀取也會完全失敗。考慮這個結構:
{ "rules": { "records": { "rec1": { ".read": true }, "rec2": { ".read": false } } } }
如果不理解規則是自動評估的,那麼獲取/records/
路徑似乎會返回rec1
而不是rec2
。然而,實際結果是錯誤的:
JavaScript
var db = firebase.database(); db.ref("records").once("value", function(snap) { // success method is not called }, function(err) { // error callback triggered with PERMISSION_DENIED });
目標-C
FIRDatabaseReference *ref = [[FIRDatabase database] reference]; [[_ref child:@"records"] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) { // success block is not called } withCancelBlock:^(NSError * _Nonnull error) { // cancel block triggered with PERMISSION_DENIED }];
迅速
var ref = FIRDatabase.database().reference() ref.child("records").observeSingleEventOfType(.Value, withBlock: { snapshot in // success block is not called }, withCancelBlock: { error in // cancel block triggered with PERMISSION_DENIED })
爪哇
FirebaseDatabase database = FirebaseDatabase.getInstance(); DatabaseReference ref = database.getReference("records"); ref.addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot snapshot) { // success method is not called } @Override public void onCancelled(FirebaseError firebaseError) { // error callback triggered with PERMISSION_DENIED }); });
休息
curl https://docs-examples.firebaseio.com/rest/records/ # response returns a PERMISSION_DENIED error
由於/records/
的讀取操作是原子的,並且沒有讀取規則授予對/records/
下所有數據的訪問權限,因此這將引發PERMISSION_DENIED
錯誤。如果我們在Firebase 控制台的安全模擬器中評估此規則,我們可以看到讀取操作被拒絕:
Attempt to read /records with auth=Success(null) / /records No .read rule allowed the operation. Read was denied.
該操作被拒絕,因為沒有讀取規則允許訪問/records/
路徑,但請注意, rec1
的規則從未被評估,因為它不在我們請求的路徑中。要獲取rec1
,我們需要直接訪問它:
JavaScript
var db = firebase.database(); db.ref("records/rec1").once("value", function(snap) { // SUCCESS! }, function(err) { // error callback is not called });
目標-C
FIRDatabaseReference *ref = [[FIRDatabase database] reference]; [[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) { // SUCCESS! }];
迅速
var ref = FIRDatabase.database().reference() ref.child("records/rec1").observeSingleEventOfType(.Value, withBlock: { snapshot in // SUCCESS! })
爪哇
FirebaseDatabase database = FirebaseDatabase.getInstance(); DatabaseReference ref = database.getReference("records/rec1"); ref.addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot snapshot) { // SUCCESS! } @Override public void onCancelled(FirebaseError firebaseError) { // error callback is not called } });
休息
curl https://docs-examples.firebaseio.com/rest/records/rec1 # SUCCESS!
位置變量
實時數據庫規則支持$location
變量來匹配路徑段。在你的路徑段前面使用$
前綴來匹配你的規則到路徑上的任何子節點。
{
"rules": {
"rooms": {
// This rule applies to any child of /rooms/, the key for each room id
// is stored inside $room_id variable for reference
"$room_id": {
"topic": {
// The room's topic can be changed if the room id has "public" in it
".write": "$room_id.contains('public')"
}
}
}
}
}
您還可以將$variable
與常量路徑名並行使用。
{
"rules": {
"widget": {
// a widget can have a title or color attribute
"title": { ".validate": true },
"color": { ".validate": true },
// but no other child paths are allowed
// in this case, $other means any key excluding "title" and "color"
"$other": { ".validate": false }
}
}
}