本指南是以「瞭解 Firebase 安全性規則語言」指南為基礎 ,示範如何在 Firebase 即時資料庫安全性規則中新增條件。
即時資料庫安全性規則的主要構成要素為 condition。A 罩杯
條件是決定特定作業的布林運算式。
是否允許或拒絕如果是基本規則,使用 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 驗證驗證使用者之後 其中包含下列屬性:
供應商 | 使用的驗證方式 (「password」、「anonymous」、「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
規則定義,僅允許採用以下格式的日期
YYYY-MM-DD (介於 1900-2099 年之間的值),系統會使用規則運算式檢查。
".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。否則傳回「否」。 |
query.orderByChild | 字串 空值 |
使用字串來表示子節點的相對路徑。例如:
query.orderByChild === "address/zip" 。如果查詢
依子節點排序,此值為空值。
|
query.startAt query.endAt query.equalTo |
字串 數字 布林值 空值 |
擷取執行中查詢的邊界,或者傳回空值 (如果有的話) 未繫結。 |
query.limitToFirst query.limitToLast |
數字 空值 |
擷取執行中查詢的限制,如果有,則傳回空值。 未設定限制 |
後續步驟
在我們討論完條件後 對Rules的瞭解並已準備好:
瞭解如何處理核心用途,並學習開發、 測試及部署 Rules:
- 瞭解您可以使用的所有預先定義 Rules 變數 來建立條件
- 寫出符合常見情境的規則。
- 深入瞭解必須找出並避免使用不安全的規則的情況,藉此增進知識。
- 瞭解 Firebase 本機模擬器套件,以及如何使用此套件測試 Rules。
- 查看可用於部署 Rules 的方法。
瞭解 Realtime Database 專屬的 Rules 功能: