安全性規則的運作方式

安全性是應用程式開發過程中,最複雜的環節之一。在大多數應用程式中,開發人員必須建構及執行伺服器,處理驗證 (使用者身分) 和授權 (使用者可執行的動作)。

Firebase Security Rules 移除中間 (伺服器) 層,並允許您為直接連線至資料的用戶端指定路徑式權限。請參閱本指南,進一步瞭解規則如何套用至傳入要求。

選取產品,進一步瞭解相關規則。

Cloud Firestore

基本結構

Firebase Security Rules 中的 Cloud FirestoreCloud Storage 使用下列結構和語法:

service <<name>> {
  // Match the resource path.
  match <<path>> {
    // Allow the request if the following conditions are true.
    allow <<methods>> : if <<condition>>
  }
}

建立規則時,請務必瞭解下列重要概念:

  • 要求:allow 陳述式中叫用的方法。這些是允許執行的方法。標準方法包括:getlistcreateupdatedeletereadwrite 便利方法可對指定資料庫或儲存空間路徑進行廣泛的讀取和寫入作業。
  • 路徑:資料庫或儲存位置,以 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 變數會解析為城市文件名稱,例如 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>;
    }
  }
}

重疊的相符陳述式

一份文件可能符合多個 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() 呼叫數量上限
  • 單一文件要求和查詢要求的上限為 10 項。
  • 多文件讀取作業、交易和批次寫入作業的上限為 20 項。上述限制 (10 項呼叫) 同樣適用於各項作業。

    舉例來說,假設您建立的批次寫入要求含有 3 項寫入作業,而您的安全性規則會使用 2 項文件存取呼叫來驗證各項寫入作業。此時,各項寫入作業會使用 2 項存取呼叫 (上限為 10 項),批次寫入要求則會使用 6 項存取呼叫 (上限為 20 項)。

超過任一項限制都會引發權限遭拒的錯誤。

系統可能會快取部分的文件存取呼叫,已快取的呼叫不會計入限制中。

巢狀 match 陳述式深度上限 10
一組巢狀 match 陳述式中允許的路徑長度上限 (以路徑區段為單位) 100
一組巢狀 match 陳述式中允許的路徑擷取變數數量上限 20
函式呼叫深度上限 20
函式引數數量上限 7
每個函式的 let 變數繫結上限 10
遞迴或循環函式呼叫的數量上限 0 (不允許)
每個要求中經評估的運算式數量上限 1,000 個
規則集的大小上限 規則集必須遵守下列兩項大小限制:
  • 如果使用 firebase deployFirebase 控制台或 CLI 發布規則集文字來源,大小限制為 256 KB。
  • Firebase 處理來源並在後端加以啟用時,所產生之已編譯規則集的大小限制為 250 KB。

Cloud Storage

基本結構

Firebase Security Rules 中的 Cloud FirestoreCloud Storage 使用下列結構和語法:

service <<name>> {
  // Match the resource path.
  match <<path>> {
    // Allow the request if the following conditions are true.
    allow <<methods>> : if <<condition>>
  }
}

建立規則時,請務必瞭解下列重要概念:

  • 要求:allow 陳述式中叫用的方法。這些是允許執行的方法。標準方法包括:getlistcreateupdatedeletereadwrite 便利方法可對指定資料庫或儲存空間路徑進行廣泛的讀取和寫入作業。
  • 路徑:資料庫或儲存位置,以 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

在規則中,如果 conditionother_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 Storagerequest 評估上傳、下載、中繼資料變更和刪除作業。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 RulesCloud Storage 提供 resource 物件中的檔案中繼資料,其中包含 Cloud Storage 物件中顯示的中繼資料鍵/值組合。您可以在 readwrite 要求中檢查這些屬性,確保資料完整性。

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

安全性規則限制

使用安全性規則時,請注意下列限制:

限制 詳細資料
每項要求的 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 結構。
  • 要求:規則會使用這些方法授予存取權。readwrite 規則會授予廣泛的讀取和寫入權限,而 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
注意:這個 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
}];
Swift
注意:這個 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
})
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
注意:這個 Firebase 產品不適用於 App Clip 目標。
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
[[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
    // SUCCESS!
}];
Swift
注意:這個 Firebase 產品不適用於 App Clip 目標。
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 }
      }
    }
  }