即時資料庫安全規則中的使用條件

本指南以學習核心 Firebase 安全規則語言指南為基礎,展示如何為 Firebase 即時資料庫安全規則新增條件。

即時資料庫安全規則的主要建構塊是條件。條件是一個布林表達式,用於確定是否應允許或拒絕特定操作。對於基本規則,使用truefalse文字作為條件效果非常好。但即時資料庫安全規則語言為您提供了編寫更複雜條件的方法,這些條件可以:

  • 檢查用戶身份驗證
  • 根據新提交的數據評估現有數據
  • 存取並比較資料庫的不同部分
  • 驗證傳入數據
  • 使用傳入查詢的結構來實現安全邏輯

使用 $ 變數捕捉路徑段

您可以透過使用$前綴聲明來擷取變數來擷取讀取或寫入路徑的部分。這充當通配符,並儲存該鍵的值以供在規則條件內使用:

{
  "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 }
    }
  }
}

驗證

最常見的安全規則模式之一是根據使用者的身份驗證狀態控制存取。例如,您的應用程式可能希望僅允許登入使用者寫入資料。

如果您的應用程式使用 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()"

引用其他路徑中的數據

任何資料都可以用作規則的標準。使用預先定義變數rootdatanewData ,我們可以存取寫入事件之前或之後存在的任何路徑。

考慮這個例子,只要/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
注意:此 Firebase 產品在 App Clip 目標上不可用。
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];
迅速
注意:此 Firebase 產品在 App Clip 目標上不可用。
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);
Objective-C
注意:此 Firebase 產品在 App Clip 目標上不可用。
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];
迅速
注意:此 Firebase 產品在 App Clip 目標上不可用。
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.表達式可在即時資料庫安全規則中使用。

基於查詢的規則表達式
表達類型描述
查詢.orderByKey
查詢.orderByPriority
查詢.orderByValue
布林值按鍵、優先權或值排序的查詢為 true。否則為假。
查詢.orderByChild細繩
無效的
使用字串表示子節點的相對路徑。例如, query.orderByChild === "address/zip" 。如果查詢不是按子節點排序的,則該值為 null。
查詢.startAt
查詢結束時間
查詢.equalTo
細繩
數位
布林值
無效的
檢索正在執行的查詢的邊界,如果沒有邊界集,則傳回 null。
查詢.limitToFirst
查詢.limitToLast
數位
無效的
檢索執行查詢的限制,如果沒有設定限制,則傳回 null。

下一步

經過對條件的討論後,您對規則有了更深入的了解,並準備好:

了解如何處理核心用例,並了解開發、測試和部署規則的工作流程:

了解特定於即時資料庫的規則功能: