Google is committed to advancing racial equity for Black communities. See how.
This page was translated by the Cloud Translation API.
Switch to English

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

本指南以學習核心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);
物鏡
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
076
迅速
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" 。如果查詢不是由子節點排序的,則此值為null。
query.startAt
query.endAt
query.equalTo


布爾值
空值
檢索執行查詢的邊界,如果沒有邊界集,則返回null。
query.limitToFirst
query.limitToLast

空值
檢索執行查詢的限制,如果未設置限制,則返回null。

下一步

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

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

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