安全規則的工作原理

安全性可能是應用程式開發難題中最複雜的部分之一。在大多數應用程式中,開發人員必須建立並執行一個伺服器來處理身份驗證(使用者是誰)和授權(使用者可以做什麼)。

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語句中呼叫的一個或多個方法。這些是您允許運行的方法。標準方法是: getlistcreateupdatedeleteread 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變數將解析為城市文件名稱,例如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 個文件存取呼叫來驗證每個寫入。在這種情況下,每個寫入使用其 10 個存取呼叫中的 2 個,而批次寫入請求使用其 20 個存取呼叫中的 6 個。

超過任一限制都會導致權限被拒絕錯誤。

某些文件存取呼叫可能會被緩存,且快取的呼叫不計入限制。

最大嵌套match語句深度10
一組嵌套match語句中允許的最大路徑長度(以路徑段為單位) 100
一組嵌套match語句中允許的最大路徑捕獲變數數20
最大函數呼叫深度20
函數參數的最大數量7
每個函數的let變數綁定的最大數量10
遞歸或循環函數呼叫的最大次數0(不允許)
每個請求計算的最大表達式數1,000
規則集的最大大小規則集必須遵守兩個大小限制:
  • 從 Firebase 控制台或使用firebase deploy從 CLI 發布的規則集文字來源的大小限制為 256 KB。
  • 當 Firebase 處理來源並使其在後端處於活動狀態時,所產生的已編譯規則集的大小限制為 250 KB。

雲端儲存

基本結構

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語句中呼叫的一個或多個方法。這些是您允許運行的方法。標準方法是: getlistcreateupdatedeleteread 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

在上面的規則中,如果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 的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 物件中顯示的元資料的鍵/值對。可以根據readwrite請求檢查這些屬性,以確保資料完整性。

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包含除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
     }
   }
 }
}

即時資料庫

基本結構

在即時資料庫中,Firebase 安全性規則由 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,則允許請求的條件。

規則如何套用於路徑

在即時資料庫中,規則以原子方式應用,這意味著較高等級父節點的規則會覆蓋更細化子節點的規則,而較深層節點的規則無法授予對父路徑的存取權。如果您已經為父路徑之一授予了存取權限,則無法細化或撤銷資料庫結構中更深層路徑的存取權限。

考慮以下規則:

{
  "rules": {
     "foo": {
        // allows read to /foo/*
        ".read": "data.child('baz').val() === true",
        "bar": {
          // ignored, since read was allowed already
          ".read": false
        }
     }
  }
}

此安全結構允許在/foo/包含值為truebaz時讀取/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
}];
迅速
注意:此 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
})
爪哇
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
注意:此 Firebase 產品在 App Clip 目標上不可用。
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
[[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
    // SUCCESS!
}];
迅速
注意:此 Firebase 產品在 App Clip 目標上不可用。
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 }
      }
    }
  }