了解 2023 年 Google I/O 大会上介绍的 Firebase 亮点。了解详情

安全規則的工作原理

安全性可能是應用程序開發難題中最複雜的部分之一。在大多數應用程序中,開發人員必須構建並運行一個服務器來處理身份驗證(用戶是誰)和授權(用戶可以做什麼)。

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語句中調用的一個或多個方法。這些是您允許運行的方法。標準方法是: getlistcreateupdatedeletereadwrite便利方法支持對指定數據庫或存儲路徑進行廣泛的讀取和寫入訪問。
  • 路徑:數據庫或存儲位置,表示為 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變量將解析為城市文檔名稱,例如SFNYC

匹配子集合

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()調用的最大次數
  • 10 用於單文檔請求和查詢請求。
  • 20 用於多文檔讀取、事務和批量寫入。之前的 10 個限制也適用於每個操作。

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

超過任一限制都會導致權限被拒絕錯誤。

某些文檔訪問調用可能會被緩存,緩存的調用不計入限制。

最大嵌套match語句深度10
一組嵌套match語句中允許的最大路徑長度(以路徑段為單位) 100
一組嵌套match語句中允許的最大路徑捕獲變量數20
最大函數調用深度20
函數參數的最大數量7
每個函數的最大let變量綁定數10
遞歸或循環函數調用的最大次數0(不允許)
每個請求評估的最大表達式數1,000
規則集的最大大小規則集必須遵守兩個大小限制:
  • 從 Firebase 控制台或使用firebase deploy從 CLI 發布的規則集文本源的大小限制為 256 KB。
  • 當 Firebase 處理源代碼並使其在後端處於活動狀態時,編譯規則集的大小限制為 250 KB。

雲儲存

基本結構

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語句中調用的一個或多個方法。這些是您允許運行的方法。標準方法是: getlistcreateupdatedeletereadwrite便利方法支持對指定數據庫或存儲路徑進行廣泛的讀取和寫入訪問。
  • 路徑:數據庫或存儲位置,表示為 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

在上面的規則中,如果conditionother_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 uidtoken ,即 Firebase 身份驗證 JWT 聲明的映射。否則,它將為null
params地圖<字符串,字符串>包含請求的查詢參數的映射。
path小路表示正在執行請求的路徑的path
resource地圖<字符串,字符串>新的資源值,僅出現在write請求中。
time時間戳表示請求被評估的服務器時間的時間戳。

資源評價

在評估規則時,您可能還想評估正在上傳、下載、修改或刪除的文件的元數據。這使您可以創建複雜而強大的規則,例如只允許上傳具有特定內容類型的文件,或者只允許刪除大於特定大小的文件。

Cloud Storage 的 Firebase 安全規則在resource對像中提供文件元數據,其中包含 Cloud Storage 對像中顯示的元數據的鍵/值對。可以在readwrite請求時檢查這些屬性以確保數據完整性。

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包含所有這些,但generationmetagenerationetagtimeCreatedupdated除外。

安全規則限制

在使用安全規則時,請注意以下限制:

限制細節
每個請求的最大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 結構。
  • 請求:這些是規則用於授予訪問權限的方法。 readwrite規則授予廣泛的讀取和寫入訪問權限,而validate規則充當輔助驗證以根據傳入或現有數據授予訪問權限。
  • 條件:如果計算結果為真,則允許請求的條件。

規則如何應用於路徑

在實時數據庫中,規則以原子方式應用,這意味著更高級別父節點的規則會覆蓋更細粒度的子節點的規則,而更深層次節點的規則無法授予對父路徑的訪問權限。如果您已經為其中一個父路徑授予訪問權限,則無法優化或撤銷對數據庫結構中更深路徑的訪問權限。

考慮以下規則:

{
  "rules": {
     "foo": {
        // allows read to /foo/*
        ".read": "data.child('baz').val() === true",
        "bar": {
          // ignored, since read was allowed already
          ".read": false
        }
     }
  }
}

此安全結構允許在/foo/包含值為truebaz時讀取/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
注意:此 Firebase 產品不適用於 App Clip 目標。
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
}];
迅速
注意:此 Firebase 產品不適用於 App Clip 目標。
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
注意:此 Firebase 產品不適用於 App Clip 目標。
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
[[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
    // SUCCESS!
}];
迅速
注意:此 Firebase 產品不適用於 App Clip 目標。
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 }
      }
    }
  }