實時數據庫安全規則中的使用條件

本指南以學習核心 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除了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.表達式在實時數據庫安全規則中可用。

基於查詢的規則表達式
表達類型描述
query.orderByKey
query.orderByPriority
query.orderByValue
布爾值True 對於按鍵、優先級或值排序的查詢。否則為假。
query.orderByChild細繩
無效的
使用字符串表示子節點的相對路徑。例如, query.orderByChild == "address/zip" 。如果查詢不是按子節點排序的,則此值為空。
查詢.startAt
查詢.endAt
查詢.equalTo
細繩
數字
布爾值
無效的
檢索執行查詢的邊界,如果沒有邊界集,則返回 null。
query.limitToFirst
query.limitToLast
數字
無效的
檢索執行查詢的限制,如果沒有設置限制,則返回 null。

下一步

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

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

了解特定於實時數據庫的規則功能: