本指南將延續「瞭解 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 Authentication 與 Firebase Realtime Database 整合,讓你可以控管資料
使用條件,對個別使用者授予存取權。使用者完成驗證後,即時資料庫安全性規則中的 auth
變數就會填入使用者資訊。這項資訊包含專屬 ID (uid
)
以及已連結的帳戶資料 (例如 Facebook ID 或電子郵件地址)
其他資訊。如果您導入了自訂驗證供應商,可以新增自己的欄位
使用者的驗證酬載
本節說明如何結合 Firebase 即時資料庫安全性規則語言,以及使用者驗證資訊。結合這兩個概念,您就能根據使用者身分控管資料存取權。
auth
變數
規則中的預先定義 auth
變數為空值,
驗證程序。
透過 Firebase 驗證驗證使用者之後 其中包含下列屬性:
供應商 | 使用的驗證方法 (「密碼」、「匿名」、「Facebook」、「GitHub」、「Google」或「Twitter」)。 |
uid | 不重複的使用者 ID,保證在所有供應商中都不得重複。 |
token |
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" } } } }
建構資料庫以支援驗證條件
以撰寫資料庫的方式建構資料庫通常很有幫助
Rules更輕鬆。在 Realtime Database 中儲存使用者資料的常見模式之一,就是將所有使用者儲存在單一 users
節點中,其子節點為每位使用者的 uid
值。如果您想限制這類資料的存取權,讓只有登入的使用者可以查看自己的資料,則規則會如下所示。
{ "rules": { "users": { "$uid": { ".read": "auth !== null && auth.uid === $uid" } } } }
使用驗證自訂憑證附加資訊
如果應用程式需要為不同使用者提供自訂存取權控管功能,Firebase Authentication 可讓開發人員設定 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);
Objective-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];
Swift
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);
Java
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);
REST
# 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);
Objective-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];
Swift
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)
Java
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);
REST
# 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。否則為 False。 |
query.orderByChild | 字串 null |
使用字串來表示子節點的相對路徑。例如 query.orderByChild === "address/zip" 。如果查詢未由子節點排序,這個值為空值。 |
query.startAt query.endAt query.equalTo |
string number boolean null |
擷取執行中查詢的邊界,或者傳回空值 (如果有的話) 未繫結。 |
query.limitToFirst query.limitToLast |
數字 null |
擷取執行中查詢的限制,如果有,則傳回空值。 未設定限制 |
後續步驟
討論條件後,您對 Rules 的瞭解將更為深入,並可進行以下操作:
瞭解如何處理核心用途,並學習開發、 測試及部署 Rules:
- 瞭解您可以使用的所有預先定義 Rules 變數 來建立條件
- 寫出符合常見情境的規則。
- 深入瞭解必須找出並避免使用不安全的規則的情況,藉此增進知識。
- 瞭解 Firebase 本機模擬器套件,以及如何使用此套件測試 Rules。
- 查看可用於部署 Rules 的方法。
瞭解 Rules 專屬於 Realtime Database 的功能: