安全性可能是應用程式開發過程中最複雜的部分之一。在大多數應用程式中,開發人員必須建構及執行伺服器,以便處理驗證 (使用者身分) 和授權 (使用者可執行的操作)。
Firebase Security Rules 移除中間 (伺服器) 層,並允許您為直接連結資料的用戶端指定路徑式權限。請參閱本指南,進一步瞭解如何將規則套用至傳入要求。
選取產品即可進一步瞭解相關規則。
Cloud Firestore
基本結構
Cloud Firestore 和 Cloud Storage 中的 Firebase Security Rules 使用以下結構和語法:
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';
設為安全性規則的第一行,才能選擇採用第 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>;
}
}
}
遞迴萬用字元
如果您想將規則套用至任意深度的階層,請使用遞迴萬用字元語法 {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 版,請參閱保護集合群組查詢。
重疊的配對陳述式
文件可能會符合多個 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 Storage
基本結構
Cloud Firestore 和 Cloud Storage 中的 Firebase Security Rules 使用以下結構和語法:
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
物件也會在 request.auth
物件中包含使用者的唯一 ID 和 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 |
時間戳記 | 代表要求評估時間的時間戳記。 |
資源評估
評估規則時,您可能也想評估上傳、下載、修改或刪除的檔案中繼資料。這可讓您建立複雜且強大的規則,例如只允許上傳特定內容類型的檔案,或只刪除超過特定大小的檔案。
Cloud Storage 的 Firebase Security Rules 會在 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 { // 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 } } } }
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 }
}
}
}