安全性規則的運作方式

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

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

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

Cloud Firestore

基本結構

Cloud FirestoreCloud 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 陳述式中叫用的一個或多個方法。這些是您允許執行的方法。標準方法為 getlistcreateupdatedeletereadwrite 便利方法可在指定資料庫或儲存路徑上啟用廣泛的讀取和寫入權限。
  • 路徑:資料庫或儲存位置,以 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 變數會解析為城市文件名稱,例如 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';,才能選擇採用第 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 項文件存取呼叫來驗證各項寫入作業。此時,各項寫入作業會使用 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

基本結構

Cloud FirestoreCloud 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 陳述式中叫用的一個或多個方法。這些是您允許執行的方法。標準方法為 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 物件也會在 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 StorageFirebase Security Rules 會在 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 {
     // 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 結構。
  • 要求:這是規則用來授予存取權的方法。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 }
      }
    }
  }