本指南以學習核心Firebase安全規則語言指南為基礎,以顯示如何向Firebase實時數據庫安全規則添加條件。
條件是實時數據庫安全規則的主要構建塊。條件是一個布爾表達式,它確定是應允許還是拒絕特定操作。對於基本規則,使用true
和false
文字作為條件非常有效。但是,實時數據庫安全規則語言為您提供了編寫更複雜條件的方法,這些條件可以:
- 檢查用戶身份驗證
- 根據新提交的數據評估現有數據
- 訪問和比較數據庫的不同部分
- 驗證傳入數據
- 將傳入查詢的結構用於安全邏輯
使用$變量捕獲路徑段
您可以通過使用$
前綴聲明捕獲變量來捕獲讀取或寫入路徑的一部分。這用作通配符,並存儲該鍵的值以在規則條件內使用:
{ "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')" } } } } }
動態$
變量也可以與恆定路徑名並行使用。在此示例中,我們使用$other
變量聲明.validate
規則,該規則確保widget
除了title
和color
之外沒有其他子代。任何會導致創建其他子代的寫操作都會失敗。
{ "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 } } } }
認證方式
最常見的安全規則模式之一是根據用戶的身份驗證狀態控制訪問。例如,您的應用可能希望僅允許登錄用戶寫入數據。
如果您的應用程序使用Firebase身份驗證,則request.auth
變量包含客戶端請求數據的身份驗證信息。有關request.auth
更多信息,請參見參考文檔。
Firebase身份驗證與Firebase實時數據庫集成在一起,可讓您使用條件控制每個用戶的數據訪問。用戶身份驗證後,您的實時數據庫安全規則規則中的auth
變量將填充用戶的信息。此信息包括其唯一標識符( uid
)以及鏈接的帳戶數據(例如Facebook ID或電子郵件地址)以及其他信息。如果實現自定義身份驗證提供程序,則可以將自己的字段添加到用戶的身份驗證有效負載中。
本部分說明如何將Firebase實時數據庫安全規則語言與有關用戶的身份驗證信息結合在一起。通過結合這兩個概念,您可以基於用戶身份控制對數據的訪問。
auth
變量
進行身份驗證之前,規則中的預定義auth
變量為null。
通過Firebase身份驗證對用戶進行身份驗證後,它將包含以下屬性:
提供者 | 使用的身份驗證方法(“密碼”,“匿名”,“ facebook”,“ github”,“ google”或“ twitter”)。 |
uid | 唯一的用戶ID,保證在所有提供商中都是唯一的。 |
代幣 | Firebase身份ID令牌的內容。有關更多詳細信息,請參見auth.token 的參考文檔。 |
這是一個示例示例規則,該規則使用auth
變量來確保每個用戶只能寫入用戶特定的路徑:
{ "rules": { "users": { "$user_id": { // grants write access to the owner of this user account // whose uid must exactly match the key ($user_id) ".write": "$user_id === auth.uid" } } } }
構建數據庫以支持身份驗證條件
通常以簡化編寫規則的方式來結構化數據庫很有幫助。在實時數據庫中存儲用戶數據的一種常見模式是將所有用戶存儲在單個users
節點中,其子節點是每個用戶的uid
值。如果您想限制對此數據的訪問,以使只有登錄的用戶可以看到他們自己的數據,則您的規則將如下所示。
{ "rules": { "users": { "$uid": { ".read": "auth != null && auth.uid == $uid" } } } }
使用身份驗證自定義聲明
對於需要針對不同用戶進行自定義訪問控制的應用,Firebase身份驗證允許開發人員對Firebase用戶設置聲明。您可以在規則的auth.token
變量中使用這些聲明。以下是使用hasEmergencyTowel
自定義聲明的規則示例:
{ "rules": { "frood": { // A towel is about the most massively useful thing an interstellar // hitchhiker can have ".read": "auth.token.hasEmergencyTowel === true" } } }
創建自己的自定義身份驗證令牌的開發人員可以選擇向這些令牌添加聲明。這些聲明可用於您規則中的auth.token
變量。
現有數據與新數據
預定義的data
變量用於在發生寫操作之前引用數據。相反, newData
變量包含如果寫入操作成功將存在的新數據。 newData
表示正在寫入的新數據和現有數據的合併結果。
為了說明這一點,該規則將允許我們創建新記錄或刪除現有記錄,但不能對現有非空數據進行更改:
// we can write as long as old data or new data does not exist // in other words, if this is a delete or a create, but not an update ".write": "!data.exists() || !newData.exists()"
在其他路徑中引用數據
任何數據都可以用作規則的標準。使用預定義的變量root
, data
和newData
,我們可以訪問寫入事件之前或之後存在的任何路徑。
考慮以下示例,只要/allow_writes/
節點的值為true
,父節點沒有設置readOnly
標誌,並且新寫入的數據中有一個名為foo
的子代,就可以進行寫操作:
".write": "root.child('allow_writes').val() === true && !data.parent().child('readOnly').exists() && newData.child('foo').exists()"
驗證數據
應該使用.validate
規則來執行數據結構的強制性驗證以及數據的格式和內容的驗證,該規則僅在.write
規則成功授予訪問權限後才運行。下面是一個示例.validate
規則定義,該規則只允許使用正則表達式檢查1900-2099年之間的YYYY-MM-DD格式的日期。
".validate": "newData.isString() && newData.val().matches(/^(19|20)[0-9][0-9][-\\/. ](0[1-9]|1[012])[-\\/. ](0[1-9]|[12][0-9]|3[01])$/)"
.validate
規則是唯一不會級聯的安全規則類型。如果任何子記錄上的任何驗證規則均失敗,則整個寫入操作將被拒絕。此外,刪除數據時(即,正在寫入的新值為null
),驗證定義將被忽略。
這些看似微不足道,但實際上是編寫強大的Firebase實時數據庫安全規則的重要功能。請考慮以下規則:
{ "rules": { // write is allowed for all paths ".write": true, "widget": { // a valid widget must have attributes "color" and "size" // allows deleting widgets (since .validate is not applied to delete rules) ".validate": "newData.hasChildren(['color', 'size'])", "size": { // the value of "size" must be a number between 0 and 99 ".validate": "newData.isNumber() && newData.val() >= 0 && newData.val() <= 99" }, "color": { // the value of "color" must exist as a key in our mythical // /valid_colors/ index ".validate": "root.child('valid_colors/' + newData.val()).exists()" } } } }
考慮到此變體,請查看以下寫入操作的結果:
的JavaScript
var ref = db.ref("/widget"); // PERMISSION_DENIED: does not have children color and size ref.set('foo'); // PERMISSION DENIED: does not have child color ref.set({size: 22}); // PERMISSION_DENIED: size is not a number ref.set({ size: 'foo', color: 'red' }); // SUCCESS (assuming 'blue' appears in our colors list) ref.set({ size: 21, color: 'blue'}); // If the record already exists and has a color, this will // succeed, otherwise it will fail since newData.hasChildren(['color', 'size']) // will fail to validate ref.child('size').set(99);
目標C
FIRDatabaseReference *ref = [[[FIRDatabase database] reference] child: @"widget"]; // PERMISSION_DENIED: does not have children color and size [ref setValue: @"foo"]; // PERMISSION DENIED: does not have child color [ref setValue: @{ @"size": @"foo" }]; // PERMISSION_DENIED: size is not a number [ref setValue: @{ @"size": @"foo", @"color": @"red" }]; // SUCCESS (assuming 'blue' appears in our colors list) [ref setValue: @{ @"size": @21, @"color": @"blue" }]; // If the record already exists and has a color, this will // succeed, otherwise it will fail since newData.hasChildren(['color', 'size']) // will fail to validate [[ref child:@"size"] setValue: @99];
迅速
var ref = FIRDatabase.database().reference().child("widget") // PERMISSION_DENIED: does not have children color and size ref.setValue("foo") // PERMISSION DENIED: does not have child color ref.setValue(["size": "foo"]) // PERMISSION_DENIED: size is not a number ref.setValue(["size": "foo", "color": "red"]) // SUCCESS (assuming 'blue' appears in our colors list) ref.setValue(["size": 21, "color": "blue"]) // If the record already exists and has a color, this will // succeed, otherwise it will fail since newData.hasChildren(['color', 'size']) // will fail to validate ref.child("size").setValue(99);
爪哇
FirebaseDatabase database = FirebaseDatabase.getInstance(); DatabaseReference ref = database.getReference("widget"); // PERMISSION_DENIED: does not have children color and size ref.setValue("foo"); // PERMISSION DENIED: does not have child color ref.child("size").setValue(22); // PERMISSION_DENIED: size is not a number Map<String,Object> map = new HashMap<String, Object>(); map.put("size","foo"); map.put("color","red"); ref.setValue(map); // SUCCESS (assuming 'blue' appears in our colors list) map = new HashMap<String, Object>(); map.put("size", 21); map.put("color","blue"); ref.setValue(map); // If the record already exists and has a color, this will // succeed, otherwise it will fail since newData.hasChildren(['color', 'size']) // will fail to validate ref.child("size").setValue(99);
休息
# PERMISSION_DENIED: does not have children color and size curl -X PUT -d 'foo' \ https://docs-examples.firebaseio.com/rest/securing-data/example.json # PERMISSION DENIED: does not have child color curl -X PUT -d '{"size": 22}' \ https://docs-examples.firebaseio.com/rest/securing-data/example.json # PERMISSION_DENIED: size is not a number curl -X PUT -d '{"size": "foo", "color": "red"}' \ https://docs-examples.firebaseio.com/rest/securing-data/example.json # SUCCESS (assuming 'blue' appears in our colors list) curl -X PUT -d '{"size": 21, "color": "blue"}' \ https://docs-examples.firebaseio.com/rest/securing-data/example.json # If the record already exists and has a color, this will # succeed, otherwise it will fail since newData.hasChildren(['color', 'size']) # will fail to validate curl -X PUT -d '99' \ https://docs-examples.firebaseio.com/rest/securing-data/example/size.json
現在讓我們看一下相同的結構,但是使用.write
規則而不是.validate
:
{ "rules": { // this variant will NOT allow deleting records (since .write would be disallowed) "widget": { // a widget must have 'color' and 'size' in order to be written to this path ".write": "newData.hasChildren(['color', 'size'])", "size": { // the value of "size" must be a number between 0 and 99, ONLY IF WE WRITE DIRECTLY TO SIZE ".write": "newData.isNumber() && newData.val() >= 0 && newData.val() <= 99" }, "color": { // the value of "color" must exist as a key in our mythical valid_colors/ index // BUT ONLY IF WE WRITE DIRECTLY TO COLOR ".write": "root.child('valid_colors/'+newData.val()).exists()" } } } }
在此變體中,以下任何操作將成功:
的JavaScript
var ref = new Firebase(URL + "/widget"); // ALLOWED? Even though size is invalid, widget has children color and size, // so write is allowed and the .write rule under color is ignored ref.set({size: 99999, color: 'red'}); // ALLOWED? Works even if widget does not exist, allowing us to create a widget // which is invalid and does not have a valid color. // (allowed by the write rule under "color") ref.child('size').set(99);
目標C
Firebase *ref = [[Firebase alloc] initWithUrl:URL]; // ALLOWED? Even though size is invalid, widget has children color and size, // so write is allowed and the .write rule under color is ignored [ref setValue: @{ @"size": @9999, @"color": @"red" }]; // ALLOWED? Works even if widget does not exist, allowing us to create a widget // which is invalid and does not have a valid color. // (allowed by the write rule under "color") [[ref childByAppendingPath:@"size"] setValue: @99];
迅速
var ref = Firebase(url:URL) // ALLOWED? Even though size is invalid, widget has children color and size, // so write is allowed and the .write rule under color is ignored ref.setValue(["size": 9999, "color": "red"]) // ALLOWED? Works even if widget does not exist, allowing us to create a widget // which is invalid and does not have a valid color. // (allowed by the write rule under "color") ref.childByAppendingPath("size").setValue(99)
爪哇
Firebase ref = new Firebase(URL + "/widget"); // ALLOWED? Even though size is invalid, widget has children color and size, // so write is allowed and the .write rule under color is ignored Map<String,Object> map = new HashMap<String, Object>(); map.put("size", 99999); map.put("color", "red"); ref.setValue(map); // ALLOWED? Works even if widget does not exist, allowing us to create a widget // which is invalid and does not have a valid color. // (allowed by the write rule under "color") ref.child("size").setValue(99);
休息
# ALLOWED? Even though size is invalid, widget has children color and size, # so write is allowed and the .write rule under color is ignored curl -X PUT -d '{size: 99999, color: "red"}' \ https://docs-examples.firebaseio.com/rest/securing-data/example.json # ALLOWED? Works even if widget does not exist, allowing us to create a widget # which is invalid and does not have a valid color. # (allowed by the write rule under "color") curl -X PUT -d '99' \ https://docs-examples.firebaseio.com/rest/securing-data/example/size.json
這說明了.write
和.validate
規則之間的差異。作為證明,所有的這些規則應該用書面.validate
,與可能的例外newData.hasChildren()
的規則,這將取決於缺失是否應該被允許。
基於查詢的規則
儘管不能將規則用作過濾器,但是可以通過在規則中使用查詢參數來限制對數據子集的訪問。使用query.
規則中的表達式,以基於查詢參數授予讀取或寫入訪問權限。
例如,下面的查詢為基礎的規則使用基於用戶的安全規則和基於查詢的規則來限制訪問數據baskets
只購物籃活躍用戶擁有的集合:
"baskets": {
".read": "auth.uid != null &&
query.orderByChild == 'owner' &&
query.equalTo == auth.uid" // restrict basket access to owner of basket
}
以下查詢(包括規則中的查詢參數)將成功:
db.ref("baskets").orderByChild("owner")
.equalTo(auth.currentUser.uid)
.on("value", cb) // Would succeed
但是,在規則中不包含參數的查詢將失敗,並顯示PermissionDenied
錯誤:
db.ref("baskets").on("value", cb) // Would fail with PermissionDenied
您還可以使用基於查詢的規則來限制客戶端通過讀取操作下載的數據量。
例如,以下規則按優先級將讀取訪問權限限制為僅查詢的前1000個結果:
messages: {
".read": "query.orderByKey &&
query.limitToFirst <= 1000"
}
// Example queries:
db.ref("messages").on("value", cb) // Would fail with PermissionDenied
db.ref("messages").limitToFirst(1000)
.on("value", cb) // Would succeed (default order by key)
以下query.
實時數據庫安全規則中提供了表達式。
基於查詢的規則表達式 | ||
---|---|---|
表達 | 類型 | 描述 |
query.orderByKey query.orderByPriority query.orderByValue | 布爾值 | 對於按鍵,優先級或值排序的查詢為True。否則為假。 |
query.orderByChild | 串 空值 | 使用字符串表示子節點的相對路徑。例如, query.orderByChild == "address/zip" 。如果查詢不是由子節點排序的,則此值為null。 |
query.startAt query.endAt query.equalTo | 串 數 布爾值 空值 | 檢索執行查詢的邊界,如果沒有邊界集,則返回null。 |
query.limitToFirst query.limitToLast | 數 空值 | 檢索執行查詢的限制,如果未設置限制,則返回null。 |
下一步
在討論條件之後,您將對規則有更深入的了解,並準備:
了解如何處理核心用例,並了解用於開發,測試和部署規則的工作流:
- 了解可用於構建條件的全套預定義的Rule變量。
- 編寫解決常見情況的規則。
- 通過回顧必鬚髮現和避免不安全規則的情況來建立自己的知識。
- 了解有關Firebase本地仿真器套件以及如何使用它測試規則的信息。
- 查看可用於部署規則的方法。
特定於實時數據庫的學習規則功能: