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

本指南是以「瞭解 Firebase 安全性規則語言」指南為基礎 ,示範如何在 Firebase 即時資料庫安全性規則中新增條件。

即時資料庫安全性規則的主要構成要素為 condition。A 罩杯 條件是決定特定作業的布林運算式。 是否允許或拒絕如果是基本規則,使用 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 系統就會以使用者的 可能不準確或不適當這項資訊包含專屬 ID (uid) 以及已連結的帳戶資料 (例如 Facebook ID 或電子郵件地址) 其他資訊。如果您導入了自訂驗證供應商,可以新增自己的欄位 使用者的驗證酬載

本節說明如何將 Firebase 即時資料庫安全性規則語言與 使用者的驗證資訊結合這兩個概念 可根據使用者身分控管資料存取權

auth 變數

規則中的預先定義 auth 變數為空值, 驗證程序。

透過 Firebase 驗證驗證使用者之後 其中包含下列屬性:

提供者 使用的驗證方式 (「password」、「anonymous」、「facebook」、「github」、「google」、 或「Twitter」)。
UID 不重複的使用者 ID,保證在所有供應商中都不得重複。
token 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 規則定義,僅允許採用以下格式的日期 YYYY-MM-DD (介於 1900-2099 年之間的值),系統會使用規則運算式檢查。

".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];
Swift
注意:這項 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);
Java
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);
REST
# 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];
Swift
注意:這項 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)
Java
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);
REST
# 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"。如果查詢 依子節點排序,此值為空值。
query.startAt
query.endAt
query.equalTo
字串
數字
布林值
空值
擷取執行中查詢的邊界,或者傳回空值 (如果有的話) 未繫結。
query.limitToFirst
query.limitToLast
數字
空值
擷取執行中查詢的限制,如果有,則傳回空值。 未設定限制

後續步驟

在我們討論完條件後 已瞭解「規則」,並準備好了:

瞭解如何處理核心用途,並學習開發、 測試及部署規則:

瞭解即時資料庫專屬的規則功能: