您可以透過 Firebase 即時資料庫安全性規則,控管資料庫中儲存資料的存取權。靈活的規則語法可讓您建立任何符合的規則,從資料庫的所有寫入作業,到個別節點上的作業皆可。
即時資料庫安全性規則是資料庫的宣告式設定。也就是說,規則與產品邏輯是分開定義的。這有許多優點:用戶端不必負責強制執行安全性,有瑕疵的實作不會影響您的資料,而最重要的是,您不需要透過中介裁判 (例如伺服器) 來保護資料。
本主題說明即時資料庫安全性規則的基本語法和結構,用於建立完整的規則集。
建立安全性規則
即時資料庫安全性規則由 JSON 文件中含有的類似 JavaScript 的運算式組成。規則的結構應遵循資料庫中儲存的資料結構。
基本規則會指出要保護的一組節點、相關的存取方法 (例如讀取、寫入),以及允許或拒絕存取的條件。在以下範例中,我們的條件將是簡單的 true
和 false
陳述式,但在下一節中,我們將介紹更具動態性的條件表達方式。
舉例來說,如果我們嘗試在 parent_node
下保護 child_node
,一般語法如下:
{ "rules": { "parent_node": { "child_node": { ".read": <condition>, ".write": <condition>, ".validate": <condition>, } } } }
讓我們套用這個模式。舉例來說,假設您要追蹤一長串訊息,並擁有如下所示的資料:
{ "messages": { "message0": { "content": "Hello", "timestamp": 1405704370369 }, "message1": { "content": "Goodbye", "timestamp": 1405704395231 }, ... } }
您的規則應以類似的方式建立。以下是適用於此資料結構的唯讀安全性規則。這個範例說明如何指定要套用規則的資料庫節點,以及在這些節點評估規則的條件。
{ "rules": { // For requests to access the 'messages' node... "messages": { // ...and the individual wildcarded 'message' nodes beneath // (we'll cover wildcarding variables more a bit later).... "$message": { // For each message, allow a read operation if <condition>. In this // case, we specify our condition as "true", so read access is always granted. ".read": "true", // For read-only behavior, we specify that for write operations, our // condition is false. ".write": "false" } } } }
基本規則操作
根據在資料上執行的作業類型,有三種強制執行安全性的規則:.write
、.read
和 .validate
。以下簡要說明這些目的:
規則類型 | |
---|---|
.read | 說明是否允許使用者讀取資料,以及允許讀取的時間。 |
.write | 說明是否允許寫入資料,以及何時允許寫入資料。 |
.validate | 定義正確格式的值應如何顯示、是否包含子項屬性,以及資料類型。 |
萬用字元擷取變數
所有規則陳述式都會指向節點。陳述式可以指向特定節點,或使用 $
萬用字元擷取變數,指向階層中某個層級的節點組合。請使用這些擷取變數來儲存節點鍵的值,以便在後續規則陳述式中使用。這項技巧可讓您編寫更複雜的 Rules 條件,我們會在下一主題中進一步說明。
{ "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 } } } }
讀取和寫入規則層疊
.read
和 .write
規則會由上而下運作,較淺層的規則會覆寫較深層的規則。如果規則授予特定路徑的讀取或寫入權限,則也會授予該路徑下所有子節點的存取權。請考慮採用下列結構:
{ "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
規則在此處沒有效果,因為子路徑無法撤銷存取權。
雖然這項功能看起來不太直覺,但這是規則語言的強大功能,可讓您以最少的努力實作非常複雜的存取權限。我們稍後會在本指南的「使用者層級安全性」一節中說明這點。
請注意,.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 });
Objective-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 }];
Swift
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 })
Java
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 }); });
REST
curl https://docs-examples.firebaseio.com/rest/records/ # response returns a PERMISSION_DENIED error
由於 /records/
的讀取作業是原子操作,且沒有任何讀取規則可授予 /records/
下所有資料的存取權,因此系統會擲回 PERMISSION_DENIED
錯誤。如果我們在 Firebase 控制台的安全模擬器中評估這項規則,就會發現讀取作業遭到拒絕,因為沒有任何讀取規則允許存取 /records/
路徑。不過,請注意,由於 rec1
不在我們要求的路徑中,因此系統從未評估 rec1
的規則。如要擷取 rec1
,我們需要直接存取:
JavaScript
var db = firebase.database(); db.ref("records/rec1").once("value", function(snap) { // SUCCESS! }, function(err) { // error callback is not called });
Objective-C
FIRDatabaseReference *ref = [[FIRDatabase database] reference]; [[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) { // SUCCESS! }];
Swift
var ref = FIRDatabase.database().reference() ref.child("records/rec1").observeSingleEventOfType(.Value, withBlock: { snapshot in // SUCCESS! })
Java
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 } });
REST
curl https://docs-examples.firebaseio.com/rest/records/rec1 # SUCCESS!
重疊陳述式
節點可能會套用多個規則。如果多個規則運算式可用於識別節點,如果「任何」條件為 false
,則會拒絕存取方法:
{ "rules": { "messages": { // A rule expression that applies to all nodes in the 'messages' node "$message": { ".read": "true", ".write": "true" }, // A second rule expression applying specifically to the 'message1` node "message1": { ".read": "false", ".write": "false" } } } }
在上述範例中,系統會拒絕對 message1
節點的讀取作業,因為第二個規則一律為 false
,即使第一個規則一律為 true
也一樣。
後續步驟
您可以進一步瞭解 Firebase 即時資料庫安全性規則:
瞭解 Rules 語言的下一個主要概念:動態條件,讓 Rules 檢查使用者授權、比較現有資料和傳入資料、驗證傳入資料、檢查來自用戶端的查詢結構等。
查看常見的安全性用途,以及用於處理這些用途的 Firebase 安全性規則定義。