瞭解即時資料庫安全性規則語言的核心語法

Firebase 即時資料庫安全規則可讓您控管資料庫中儲存資料的存取權。彈性的規則語法可讓您建立規則,比對資料庫的所有寫入作業,以及個別節點上的作業。

即時資料庫安全性規則是資料庫的宣告式設定,也就是說,規則是與產品邏輯分開定義。這有許多優點:用戶端不必負責強制執行安全性,有錯誤的實作方式不會危及資料,或許最重要的是,不需要中介裁判 (例如伺服器) 來保護資料免於外界侵害。

本主題說明建立完整規則集時使用的即時資料庫安全性規則基本語法和結構。

建立安全性規則

即時資料庫安全性規則是由 JSON 文件中類似 JavaScript 的運算式組成。規則結構應與儲存在資料庫中的資料結構一致。

基本規則會識別要保護的一組節點、涉及的存取方法 (例如讀取、寫入),以及允許或拒絕存取權的條件。在下列範例中,我們的條件會是簡單的 truefalse 陳述式,但在下一個主題中,我們會介紹更多動態表達條件的方式。

舉例來說,如果我們嘗試保護 child_node 下的 parent_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 除了 titlecolor 以外,沒有其他子項。 如果寫入作業會導致建立其他子項,就會失敗。

{
  "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
注意:這個 Firebase 產品不適用於 App Clip 目標。
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
注意:這個 Firebase 產品不適用於 App Clip 目標。
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,我們需要直接存取該物件:

JavaScript
var db = firebase.database();
db.ref("records/rec1").once("value", function(snap) {
  // SUCCESS!
}, function(err) {
  // error callback is not called
});
Objective-C
注意:這個 Firebase 產品不適用於 App Clip 目標。
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
[[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
    // SUCCESS!
}];
Swift
注意:這個 Firebase 產品不適用於 App Clip 目標。
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 安全性規則定義