安全性是應用程式開發過程中,最複雜的環節之一。在大多數應用程式中,開發人員必須建構及執行伺服器,處理驗證 (使用者身分) 和授權 (使用者可執行的動作)。
Firebase Security Rules 移除中間 (伺服器) 層,並允許您為直接連線至資料的用戶端指定路徑式權限。請參閱本指南,進一步瞭解規則如何套用至傳入要求。
選取產品,進一步瞭解相關規則。
Cloud Firestore
基本結構
Firebase Security Rules 中的 Cloud Firestore 和 Cloud Storage 使用下列結構和語法:
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 月起,您可以使用第 2 版Firebase安全性規則。規則第 2 版會變更遞迴萬用字元 {name=**}
的行為。如要使用集合群組查詢,請務必使用第 2 版。您必須在安全規則中加入 rules_version = '2';
做為第一行,才能選擇採用第 2 版:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
相符路徑
所有比對陳述式都應指向文件,而非集合。比對陳述式可以指向特定文件 (如 match /cities/SF
),也可以使用萬用字元指向指定路徑中的任何文件 (如 match /cities/{city}
)。
在這個範例中,比對陳述式使用 {city}
萬用字元語法。
也就是說,這項規則適用於 cities
集合中的任何文件,例如 /cities/SF
或 /cities/NYC
。評估比對陳述式中的 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>;
}
}
}
重疊的相符陳述式
一份文件可能符合多個 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.
match /cities/{document} {
allow read, write: if true;
}
}
}
在本例中,系統會允許對 cities
集合的所有讀取和寫入作業,因為第二個規則一律為 true
,即使第一個規則一律為 false
也是如此。
遞迴萬用字元
如要將規則套用至任意深度的階層,請使用遞迴萬用字元語法 {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';
,才能選擇使用第 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 版,請參閱保護集合群組查詢。
安全性規則限制
使用安全性規則時,請注意下列限制:
限制 | 說明 |
---|---|
每項要求的 exists() 、get() 和 getAfter() 呼叫數量上限 |
超過任一項限制都會引發權限遭拒的錯誤。 系統可能會快取部分的文件存取呼叫,已快取的呼叫不會計入限制中。 |
巢狀 match 陳述式深度上限 |
10 |
一組巢狀 match 陳述式中允許的路徑長度上限 (以路徑區段為單位) |
100 |
一組巢狀 match 陳述式中允許的路徑擷取變數數量上限 |
20 |
函式呼叫深度上限 | 20 |
函式引數數量上限 | 7 |
每個函式的 let 變數繫結上限 |
10 |
遞迴或循環函式呼叫的數量上限 | 0 (不允許) |
每個要求中經評估的運算式數量上限 | 1,000 個 |
規則集的大小上限 | 規則集必須遵守下列兩項大小限制:
|
Cloud Storage
基本結構
Firebase Security Rules 中的 Cloud Firestore 和 Cloud Storage 使用下列結構和語法:
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 Security Rules 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
評估為 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 Security Rules 不會層疊,且只有在要求路徑與指定規則的路徑相符時,才會評估規則。
要求評估
系統會使用傳送至 Cloud Storage 的 request
評估上傳、下載、中繼資料變更和刪除作業。request
變數包含執行要求的路徑、收到要求的時間,以及要求是否為寫入作業,如果是,則包含新的 resource
值。也包含 HTTP 標頭和驗證狀態。
request
物件也包含使用者的專屬 ID,以及 request.auth
物件中的 Firebase Authentication 酬載,詳情請參閱文件中的「驗證」一節。
以下列出 request
物件的所有屬性:
屬性 | 類型 | 說明 |
---|---|---|
auth |
map<string, string> | 使用者登入後,系統會提供 uid 、使用者專屬 ID,以及 token (Firebase Authentication JWT 憑證附加資訊的地圖)。否則為 null 。 |
params |
map<string, string> | 包含要求查詢參數的地圖。 |
path |
路徑 | path ,代表要求執行的路徑。 |
resource |
map<string, string> | 新的資源值,僅適用於 write 要求。 |
time |
時間戳記 | 時間戳記,代表評估要求時的伺服器時間。 |
資源評估
評估規則時,您可能也想評估上傳、下載、修改或刪除檔案的中繼資料。這項功能可讓您建立複雜且強大的規則,例如只允許上傳特定內容類型的檔案,或只允許刪除大於特定大小的檔案。
Firebase Security Rules 的 Cloud Storage 提供 resource
物件中的檔案中繼資料,其中包含 Cloud Storage 物件中顯示的中繼資料鍵/值組合。您可以在 read
或 write
要求中檢查這些屬性,確保資料完整性。
在 write
要求 (例如上傳、中繼資料更新和刪除) 中,除了 resource
物件 (包含要求路徑中現有檔案的檔案中繼資料) 之外,您也可以使用 request.resource
物件,其中包含一組檔案中繼資料,如果允許寫入,就會寫入這些資料。您可以使用這兩個值確保資料完整性,或強制執行檔案類型或大小等應用程式限制。
如要查看 resource
物件的完整屬性清單,請參閱:
屬性 | 類型 | 說明 |
---|---|---|
name |
字串 | 物件的全名 |
bucket |
字串 | 這個物件所在的值區名稱。 |
generation |
int | 這個物件的Google Cloud Storage 物件產生。 |
metageneration |
int | 這個物件的 Google Cloud Storage 物件中繼資料產生編號。 |
size |
int | 物件大小 (以位元組為單位)。 |
timeCreated |
時間戳記 | 代表物件建立時間的時間戳記。 |
updated |
時間戳記 | 代表物件上次更新時間的時間戳記。 |
md5Hash |
字串 | 物件的 MD5 雜湊。 |
crc32c |
字串 | 物件的 crc32c 雜湊。 |
etag |
字串 | 與這個物件相關聯的 etag。 |
contentDisposition |
字串 | 與這個物件相關聯的內容處置。 |
contentEncoding |
字串 | 與這個物件相關聯的內容編碼。 |
contentLanguage |
字串 | 與這個物件相關聯的內容語言。 |
contentType |
字串 | 與這個物件相關聯的內容類型。 |
metadata |
map<string, string> | 開發人員指定的其他自訂中繼資料鍵/值組合。 |
request.resource
包含所有這些項目,但 generation
、metageneration
、etag
、timeCreated
和 updated
除外。
安全性規則限制
使用安全性規則時,請注意下列限制:
限制 | 詳細資料 |
---|---|
每項要求的 firestore.exists() 和 firestore.get() 呼叫數量上限 |
單一文件要求和查詢要求的上限為 2 項。 超過這項限制會導致權限遭拒的錯誤。 系統可能會快取對相同文件的存取呼叫,已快取的呼叫不會計入限制中。 |
完整範例
將所有項目集中在一起,您就能為圖片儲存解決方案建立完整的規則範例:
service firebase.storage { match /b/{bucket}/o { match /images { // 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) Filename (stored in imageId wildcard variable) is less than 32 characters match /{imageId} { allow read; allow write: if request.resource.size < 5 * 1024 * 1024 && request.resource.contentType.matches('image/.*') && request.resource.contentType == resource.contentType && imageId.size() < 32 } } } }
Realtime Database
基本結構
在 Realtime Database 中,Firebase Security Rules 包含 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,則允許要求。
規則如何套用至路徑
在 Realtime Database 中,Rules 會以原子方式套用,也就是說,較高層級父項節點的規則會覆寫較細微子項節點的規則,而較深層節點的規則無法授予父項路徑的存取權。如果已授予其中一個父項路徑的存取權,就無法在資料庫結構中更深層的路徑,調整或撤銷存取權。
請參考以下規則:
{ "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 }];
Swift
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 })
Java
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 }); });
REST
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! }];
Swift
var ref = FIRDatabase.database().reference() ref.child("records/rec1").observeSingleEventOfType(.Value, withBlock: { snapshot in // SUCCESS! })
Java
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 } });
REST
curl https://docs-examples.firebaseio.com/rest/records/rec1 # SUCCESS!
地點變數
Realtime Database Rules 支援 $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 }
}
}
}