Join us for Firebase Summit on November 10, 2021. Tune in to learn how Firebase can help you accelerate app development, release with confidence, and scale with ease. Register

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

該指南基於學習的核心火力地堡安全規則的語言指南,說明如何條件添加到您的火力地堡實時數據庫安全規則。

實時數據庫安全規則的主要組成部分是條件。條件是一個布爾表達式,用於確定應允許還是拒絕特定操作。對於基本規則,使用truefalse文字作為條件工作prefectly很好。但是實時數據庫安全規則語言為您提供了編寫更複雜條件的方法,這些條件可以:

  • 檢查用戶身份驗證
  • 根據新提交的數據評估現有數據
  • 訪問和比較數據庫的不同部分
  • 驗證傳入數據
  • 將傳入查詢的結構用於安全邏輯

使用 $ 變量捕獲路徑段

您可以通過聲明捕獲變量與捕捉讀或寫路徑的部分$前綴。這用作通配符,並存儲該鍵的值以供在規則條件內使用:

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

驗證

最常見的安全規則模式之一是根據用戶的身份驗證狀態控制訪問。例如,您的應用可能希望僅允許登錄用戶寫入數據。

如果應用程序使用火力地堡身份驗證,則request.auth變量包含客戶端請求數據的驗證信息。有關更多信息request.auth ,請參閱參考文檔

Firebase 身份驗證與 Firebase 實時數據庫集成,允許您使用條件控制每個用戶的數據訪問。一旦用戶通過驗證,該auth在你的實時數據庫安全規則的規則變量將用戶的信息填充。此信息包括其唯一標識符( uid )以及關聯的帳戶數據,諸如Facebook的ID或電子郵件地址,以及其他信息。如果您實現自定義身份驗證提供程序,則可以將自己的字段添加到用戶的身份驗證負載中。

本部分介紹瞭如何將 Firebase 實時數據庫安全規則語言與用戶的身份驗證信息結合起來。通過結合這兩個概念,您可以根據用戶身份控制對數據的訪問。

auth變量

預定義的auth的規則變量為空認證發生之前。

一旦用戶與認證火力地堡驗證它包含以下屬性:

提供者使用的身份驗證方法(“密碼”、“匿名”、“facebook”、“github”、“google”或“twitter”)。
用戶界面唯一的用戶 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"
      }
    }
  }
}

使用身份驗證自定義聲明

對於需要針對不同的用戶自定義訪問控制應用,火力地堡認證允許開發者在火力地堡用戶組聲明。這些說法是在入店auth.token規則中的變量。這裡的規則,利用的例子hasEmergencyTowel定制要求:

{
  "rules": {
    "frood": {
      // A towel is about the most massively useful thing an interstellar
      // hitchhiker can have
      ".read": "auth.token.hasEmergencyTowel === true"
    }
  }
}

開發人員創建自己的定制身份驗證令牌可以選擇添加聲稱這些令牌。這些說法是對avaialble 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規則定義只允許日期格式YYYY-MM-DD的年2000至99年間,這是使用正則表達式檢查。

".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);
目標-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];
迅速
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);
目標-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];
迅速
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
布爾值對於按鍵、優先級或值排序的查詢為真。否則為假。
query.orderByChild細繩
空值
使用字符串表示子節點的相對路徑。例如, query.orderByChild == "address/zip"如果查詢不是按子節點排序,則此值為空。
query.startAt
查詢.endAt
查詢.equalTo
細繩
數字
布爾值
空值
檢索正在執行的查詢的邊界,如果沒有設置邊界,則返回 null。
query.limitToFirst
query.limitToLast
數字
空值
檢索正在執行的查詢的限制,如果沒有設置限制,則返回 null。

下一步

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

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

學習特定於實時數據庫的規則功能: