Check out what’s new from Firebase@ Google I/O 2021, and join our alpha program for early access to the new Remote Config personalization feature. Learn more

安全規則的工作原理

安全性可能是應用程序開發難題中最複雜的部分之一。在大多數應用程序中,開發人員必須構建和運行處理身份驗證(用戶是誰)和授權(用戶可以做什麼)的服務器。

Firebase 安全規則移除了中間(服務器)層,並允許您為直接連接到您的數據的客戶端指定基於路徑的權限。使用本指南了解有關如何將規則應用於傳入請求的更多信息。

選擇產品以了解有關其規則的更多信息。

雲防火牆

基本結構

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語句中調用的一個或多個方法。這些是您允許運行的方法。標準方法是: getlistcreateupdatedelete 。的readwrite方便的方法使指定的數據庫或存儲路徑上廣泛的讀取和寫入訪問。
  • 路徑:數據庫或存儲位置,表示為 URI 路徑。
  • 規則: allow語句,其中包含一個條件,該條件允許請求的計算結果為真。

安全規則版本 2

自 2019 年 5 月起,Firebase 安全規則的第 2 版現已可用。規則的第 2 版更改了遞歸通配符{name=**}的行為。如果您計劃使用集合組查詢,則必須使用版本 2。您必須通過設置rules_version = '2';來選擇加入版本 2 rules_version = '2';安全規則中的第一行:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

匹配路徑

所有匹配語句都應該指向文檔,而不是集合。 match 語句可以指向特定文檔,如match /cities/SF或使用通配符指向指定路徑中的任何文檔,如match /cities/{city}

在上面的示例中,match 語句使用{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_towerdocument ,並且document變量的值將是SF/landmarks/coit_tower

但是請注意,遞歸通配符的行為取決於規則版本。

版本 1

默認情況下,安全規則使用版本 1。在版本 1 中,遞歸通配符匹配一個或多個路徑項。它們不匹配空路徑,因此match /cities/{city}/{document=**}匹配子集合中的文檔但不match /cities/{document=**} cities集合中的文檔,而match /cities/{document=**}匹配子集合中的兩個文檔cities集合和子集合。

遞歸通配符必須出現在匹配語句的末尾。

版本 2

在安全規則的第 2 版中,遞歸通配符匹配零個或多個路徑項。 match/cities/{city}/{document=**}匹配任何子集合中的文檔以及cities集合中的文檔。

您必須通過添加rules_version = '2';來選擇加入第 2 版rules_version = '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>;
    }
  }
}

每個 match 語句最多可以有一個遞歸通配符,但在版本 2 中,您可以將此通配符放在 match 語句中的任何位置。例如:

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語句中允許的最大路徑長度(在路徑段match 100
一組嵌套match語句中允許的最大路徑捕獲變量數20
最大函數調用深度20
函數參數的最大數量7
每個函數的let變量綁定的最大數量10
遞歸或循環函數調用的最大數量0(不允許)
每個請求評估的最大表達式數1,000
規則集的最大大小Verax 規則集必須遵守兩個大小限制:
  • 從 Firebase 控制台或使用firebase deploy的 CLI 發布的 Verax 規則集文本源的大小限制為 256 KB。
  • 當 Firebase 處理 Verax 源並使其在後端處於活動狀態時,編譯規則集的大小限制為 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語句中調用的一個或多個方法。這些是您允許運行的方法。標準方法是: getlistcreateupdatedelete 。的readwrite方便的方法使指定的數據庫或存儲路徑上廣泛的讀取和寫入訪問。
  • 路徑:數據庫或存儲位置,表示為 URI 路徑。
  • 規則: allow語句,其中包含一個條件,該條件允許請求的計算結果為真。

匹配路徑

Cloud Storage 安全規則match用於訪問 Cloud Storage match的文件的文件路徑相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

上面的規則中,文件“images/profilePhoto.png”可以在conditionother_condition計算結果為true時讀取,而文件“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 安全規則不會級聯,並且僅當請求路徑與指定規則的路徑匹配時才會評估規則。

請求評估

使用發送到 Cloud Storage 的request評估上傳、下載、元數據更改和刪除。 request變量包含執行request的文件路徑、接收請求的時間以及如果請求是寫入的新resource值。還包括 HTTP 標頭和身份驗證狀態。

request像還包含用戶的唯一 ID 和request.auth像中的 Firebase 身份驗證有效負載,這將在文檔的身份驗證部分進一步解釋。

request像中的完整屬性列表如下:

財產類型描述
auth地圖<字符串,字符串>當用戶登錄時,提供uid ,用戶的唯一 ID 和token ,Firebase Authentication JWT 聲明的映射。否則,它將為null
params地圖<字符串,字符串>包含請求的查詢參數的映射。
path小路表示正在執行請求的path的路徑。
resource地圖<字符串,字符串>新的資源值,僅在write請求時出現。
time時間戳表示評估請求的服務器時間的時間戳。

資源評估

在評估規則時,您可能還想評估正在上傳、下載、修改或刪除的文件的元數據。這使您可以創建複雜而強大的規則,以執行諸如僅允許上傳具有特定內容類型的文件或僅允許刪除大於特定大小的文件之類的操作。

適用於 Cloud Storage 的 Firebase 安全規則在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 timeCreated

完整示例

綜上所述,您可以為圖像存儲解決方案創建完整的規則示例:

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規則充當基於傳入或現有數據的二次驗證授予訪問權限。
  • 條件:如果評估為真則允許請求的條件。

規則如何應用於路徑

在實時數據庫中,規則以原子方式應用,這意味著更高級別父節點的規則會覆蓋更細粒度的子節點的規則,更深節點的規則無法授予對父路徑的訪問權限。如果您已經為父路徑之一授予了訪問權限,則無法在數據庫結構中的更深路徑上優化或撤消訪問權限。

考慮以下規則:

{
  "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規則在這裡不起作用,因為訪問不能".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
});
目標-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
}];
迅速
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
});
目標-C
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
[[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
    // SUCCESS!
}];
迅速
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 }
      }
    }
  }