Join us for Firebase Summit on November 10, 2021. Tune in to learn how Firebase can help you accelerate app development, release with confidence, and scale with ease. Register

安全規則的工作原理

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

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陳述,其中包括允許請求,如果計算結果為true的條件。

安全規則版本 2

自 2019 年 5 月起,Firebase 安全規則的第 2 版現已可用。規則2版改變的行為遞歸通配符{name=**}如果你打算使用,則必須使用版本2 ,收集小組查詢。你必須做選擇加入2版本rules_version = '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集合。

你必須通過添加選擇加入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語句100
路徑捕獲變量的最大數量允許的一組嵌套的內部match聲明20
最大函數調用深度20
函數參數的最大數量7
最大數量let每個函數變量綁定10
遞歸或循環函數調用的最大數量0(不允許)
每個請求評估的最大表達式數1,000
規則集的最大大小規則集必須遵守兩個大小限制:
  • 從火力地堡控制台或使用CLI公佈的規則集文本源的大小256 KB極限firebase deploy
  • 當 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聲明。這些是您允許運行的方法。標準的方法是: getlistcreateupdatedelete 。的readwrite方便的方法使指定的數據庫或存儲路徑上廣泛的讀取和寫入訪問。
  • 路徑:數據庫或存儲位置,表示為URI路徑。
  • 規則: allow陳述,其中包括允許請求,如果計算結果為true的條件。

匹配路徑

雲存儲安全規則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

在上面的規則,文件“的圖像/ profilePhoto.png”可以如果任一被讀取conditionother_condition評價為真,而文件“的圖像/用戶/用戶: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對象,這將進一步在說明驗證的文檔的部分。

在屬性的完整清單request對象可用下面:

財產類型描述
auth地圖<字符串,字符串>當用戶登錄,提供uid ,用戶的唯一ID和token ,映射一個火力地堡認證JWT權利要求中。否則,這將是null
params地圖<字符串,字符串>包含請求的查詢參數的映射。
path小路path表示正在以執行請求的路徑。
resource地圖<字符串,字符串>新的資源價值,在目前唯一write請求。
time時間戳表示評估請求的服務器時間的時間戳。

資源評估

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

火力地堡安全規則的雲存儲提供的文件元數據resource對象,其中包含元數據的鍵/值對在雲存儲對象浮出水面。這些性質可以對被檢查readwrite請求,以確保數據的完整性。

write請求(如上傳,元數據更新和刪除),除了resource對象,它包含了目前存在於請求路徑,你也必須使用的能力,文件文件元數據request.resource對象,如果允許寫入,它包含要寫入的文件元數據的子集。您可以使用這兩個值來確保數據完整性或強制應用程序約束,例如文件類型或大小。

在屬性的完整列表resource對象可用如下:

財產類型描述
name細繩對象的全名
bucket細繩此對象所在的存儲桶的名稱。
generation整數谷歌雲存儲對象生成此對象的。
metageneration整數谷歌Cloud Storage物件metageneration此對象。
size整數對象的大小(以字節為單位)。
timeCreated時間戳表示對象創建時間的時間戳。
updated時間戳表示對像上次更新時間的時間戳。
md5Hash細繩對象的 MD5 哈希值。
crc32c細繩對象的 crc32c 哈希。
etag細繩與此對象關聯的 etag。
contentDisposition細繩與此對象關聯的內容處置。
contentEncoding細繩與此對象關聯的內容編碼。
contentLanguage細繩與此對象關聯的內容語言。
contentType細繩與此對象關聯的內容類型。
metadata地圖<字符串,字符串>額外的、開發人員指定的自定義元數據的鍵/值對。

request.resource包含了所有這些與外generationmetagenerationetagtimeCreatedupdated

完整示例

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

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
        }
     }
  }
}

這種安全結構允許/bar/從每當讀/foo/包含子baz值為true 。該".read": false根據規則/foo/bar/這裡有沒有效果,因為訪問不能由孩子的路徑被撤銷。

雖然它可能看起來並不直觀,但它是規則語言的一個強大部分,允許以最少的努力實現非常複雜的訪問權限。本作是特別有用的基於用戶的安全

然而, .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錯誤。如果我們評估我們的安全模擬器這個規則火力地堡控制台,我們可以看到,讀操作被拒絕:

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 }
      }
    }
  }