安全性可能是應用程式開發難題中最複雜的部分之一。在大多數應用程式中,開發人員必須建立並執行一個伺服器來處理身份驗證(使用者是誰)和授權(使用者可以做什麼)。
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>>
}
}
在建構規則時,理解以下關鍵概念非常重要:
- 請求:
allow
語句中呼叫的一個或多個方法。這些是您允許運行的方法。標準方法是:get
、list
、create
、update
和delete
。read
便利方法可以對指定的資料庫或儲存路徑write
廣泛的讀寫存取。 - 路徑:資料庫或儲存位置,表示為 URI 路徑。
- 規則:
allow
語句,其中包含一個條件,如果該條件的計算結果為 true,則允許請求。
安全規則版本 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>>
}
}
在建構規則時,理解以下關鍵概念非常重要:
- 請求:
allow
語句中呼叫的一個或多個方法。這些是您允許運行的方法。標準方法是:get
、list
、create
、update
和delete
。read
write
方法可以對指定的資料庫或儲存路徑進行廣泛的讀寫存取。 - 路徑:資料庫或儲存位置,表示為 URI 路徑。
- 規則:
allow
語句,其中包含一個條件,如果該條件的計算結果為 true,則允許請求。
匹配路徑
Cloud Storage 安全規則與用於存取 Cloud Storage 中的檔案的檔案路徑match
。規則可以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
計算結果為true,則可以讀取檔案“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 | 地圖<字串,字串> | 當使用者登入時,提供uid (使用者的唯一 ID)和token (Firebase 驗證 JWT 聲明的對應)。否則,它將為null 。 |
params | 地圖<字串,字串> | 包含請求的查詢參數的對應。 |
path | 小路 | 表示正在執行請求的路徑的path 。 |
resource | 地圖<字串,字串> | 新的資源值僅在write 請求時出現。 |
time | 時間戳 | 表示評估請求的伺服器時間的時間戳記。 |
資源評估
在評估規則時,您可能還需要評估正在上傳、下載、修改或刪除的檔案的元資料。這允許您創建複雜而強大的規則,執行諸如僅允許上傳特定內容類型的文件,或僅刪除大於特定大小的文件等操作。
Firebase Cloud Storage 安全性規則在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
規則則充當輔助驗證,根據傳入或現有資料授予存取權限。 - 條件:如果評估結果為 true,則允許請求的條件。
規則如何套用於路徑
在即時資料庫中,規則以原子方式應用,這意味著較高等級父節點的規則會覆蓋更細化子節點的規則,而較深層節點的規則無法授予對父路徑的存取權。如果您已經為父路徑之一授予了存取權限,則無法細化或撤銷資料庫結構中更深層路徑的存取權限。
考慮以下規則:
{ "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 });
Objective-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 });
Objective-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 }
}
}
}