了解 Realtime Database 安全规则语言的核心语法

Firebase Realtime Database 安全规则允许您控制对数据库中存储的数据的访问权限。灵活的规则语法允许您创建与任意情况匹配的规则,涵盖对数据库执行的所有写入操作和对各个节点的操作等各种场景。

Realtime Database 安全规则是针对数据库的声明性配置。这意味着这些规则的定义独立于产品逻辑之外。这种方法具有许多优点:客户端不负责强制执行安全策略,即使有缺陷的实现也不会损坏您的数据,而且或许最重要的一点是,无需中间受托对象(例如服务器)来保护数据。

本主题介绍 Realtime Database 安全规则用于创建完整规则集的基本语法和结构。

构建安全规则

Realtime Database 安全规则由类似 JavaScript 的表达式(包含在 JSON 文件中)构成。规则的结构应符合您在数据库中存储的数据的结构。

基本规则可识别一组要保护的节点、所涉及的“访问方法”(例如,读取、写入)和允许或拒绝访问的“条件”。在以下示例中,我们的条件将是简单的 truefalse 语句,但在下一个主题中,我们将介绍更多用于表达条件的动态方式。

例如,如果我们想要确保 parent_nodechild_node 的安全,应遵循以下常规语法:

{
  "rules": {
    "parent_node": {
      "child_node": {
        ".read": <condition>,
        ".write": <condition>,
        ".validate": <condition>,
      }
    }
  }
}

让我们采用此模式。例如,假设您要跟踪消息列表,且您具有如下所示的数据:

{
  "messages": {
    "message0": {
      "content": "Hello",
      "timestamp": 1405704370369
    },
    "message1": {
      "content": "Goodbye",
      "timestamp": 1405704395231
    },
    ...
  }
}

您的规则应以类似的方式构建。以下是一组可能适用于此数据结构的只读安全规则。此示例说明了我们如何指定规则适用的数据库节点,以及用于在这些节点评估规则的条件。

{
  "rules": {
    // For requests to access the 'messages' node...
    "messages": {
      // ...and the individual wildcarded 'message' nodes beneath
      // (we'll cover wildcarding variables more a bit later)....
      "$message": {

        // For each message, allow a read operation if <condition>. In this
        // case, we specify our condition as "true", so read access is always granted.
        ".read": "true",

        // For read-only behavior, we specify that for write operations, our
        // condition is false.
        ".write": "false"
      }
    }
  }
}

基本规则操作

根据对数据执行的操作类型,可以使用三种类型的规则来强制执行安全性:.write.read.validate。下面简要介绍了这三种规则的用途:

规则类型
.read 描述是否以及何时允许用户读取数据。
.write 描述是否以及何时允许写入数据。
.validate 定义值的正确格式、值是否包含子属性以及值的数据类型。

通配符捕获变量

所有规则语句都指向节点。一条语句可以指向特定节点,也可以使用 $ 通配符捕获变量以指向层次结构级别的节点集。使用这些捕获变量来存储节点键的值,以便在后续规则语句中使用。这种方法可让您编写更复杂的规则条件,我们将在下一个主题中对此进行详细介绍。

{
  "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 规则,该规则确保除了 titlecolor 之外,widget 没有其他子节点。任何会导致创建其他子节点的写入均将失败。

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

读取和写入规则级联

.read.write 规则自上而下发挥作用,浅层规则将替换深层规则。如果某个规则授予对特定路径的读取或写入权限,则也会授予对此路径下所有子节点的访问权限。请参考以下结构:

{
  "rules": {
     "foo": {
        // allows read to /foo/*
        ".read": "data.child('baz').val() === true",
        "bar": {
          /* ignored, since read was allowed already */
          ".read": false
        }
     }
  }
}

只要 /foo/ 包含值为 true 的子节点 baz,此安全结构即允许读取 /bar//foo/bar/ 之下的 ".read": false 规则此时不起作用,因为子路径无法撤销访问权限。

虽然看起来不是很直观,但这是该规则语言十分强大的一个功能,可用最少的工作量实现非常复杂的访问特权。本指南稍后会在介绍基于用户的安全时,对此加以说明。

请注意,.validate 规则不会进行级联。如需执行写入,必须满足层次结构中各个层级的所有验证规则。

规则并非过滤条件

规则以原子方式应用。这表示,如果授予访问权限的位置或父位置没有规则,读取或写入操作将立即失败。即使所有相关子路径均可访问,在父位置的读取操作也会完全失败。请参考以下结构:

{
  "rules": {
    "records": {
      "rec1": {
        ".read": true
      },
      "rec2": {
        ".read": false
      }
    }
  }
}

如果不理解规则是以原子方式进行求值,这看起来可能就像提取 /records/ 路径会返回 rec1,而不是 rec2。但实际的结果是一个错误:

JavaScript
var db = firebase.database();
db.ref("records").once("value", function(snap) {
  // success method is not called
}, function(err) {
  // error callback triggered with PERMISSION_DENIED
});
Objective-C
注意:此 Firebase 产品不适用于 App Clip 目标。
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
[[_ref child:@"records"] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
  // success block is not called
} withCancelBlock:^(NSError * _Nonnull error) {
  // cancel block triggered with PERMISSION_DENIED
}];
Swift
注意:此 Firebase 产品不适用于 App Clip 目标。
var ref = FIRDatabase.database().reference()
ref.child("records").observeSingleEventOfType(.Value, withBlock: { snapshot in
    // success block is not called
}, withCancelBlock: { error in
    // cancel block triggered with PERMISSION_DENIED
})
Java
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference("records");
ref.addListenerForSingleValueEvent(new ValueEventListener() {
  @Override
  public void onDataChange(DataSnapshot snapshot) {
    // success method is not called
  }

  @Override
  public void onCancelled(FirebaseError firebaseError) {
    // error callback triggered with PERMISSION_DENIED
  });
});
REST
curl https://docs-examples.firebaseio.com/rest/records/
# response returns a PERMISSION_DENIED error

由于在 /records/ 的读取操作以原子方式进行,且没有任何读取规则授予对 /records/ 下所有数据的访问权限,因此将产生 PERMISSION_DENIED 错误。如果在 Firebase 控制台的安全模拟器中对此规则求值,就可以看到该读取操作被拒绝,因为没有允许访问 /records/ 路径的读取规则:但是,需要注意的是,对 rec1 的规则始终未被求值,因为它不在我们请求的路径中。如需提取 rec1,我们需要直接对其进行访问:

JavaScript
var db = firebase.database();
db.ref("records/rec1").once("value", function(snap) {
  // SUCCESS!
}, function(err) {
  // error callback is not called
});
Objective-C
注意:此 Firebase 产品不适用于 App Clip 目标。
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
[[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
    // SUCCESS!
}];
Swift
注意:此 Firebase 产品不适用于 App Clip 目标。
var ref = FIRDatabase.database().reference()
ref.child("records/rec1").observeSingleEventOfType(.Value, withBlock: { snapshot in
    // SUCCESS!
})
Java
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference("records/rec1");
ref.addListenerForSingleValueEvent(new ValueEventListener() {
  @Override
  public void onDataChange(DataSnapshot snapshot) {
    // SUCCESS!
  }

  @Override
  public void onCancelled(FirebaseError firebaseError) {
    // error callback is not called
  }
});
REST
curl https://docs-examples.firebaseio.com/rest/records/rec1
# SUCCESS!

重叠的语句

一个节点可能适用多个规则。在多个规则表达式识别一个节点的情况下,如果有任何条件为 false,则访问方法将被拒绝:

{
  "rules": {
    "messages": {
      // A rule expression that applies to all nodes in the 'messages' node
      "$message": {
        ".read": "true",
        ".write": "true"
      },
      // A second rule expression applying specifically to the 'message1` node
      "message1": {
        ".read": "false",
        ".write": "false"
      }
    }
  }
}

在上面的示例中,因为第二个规则始终为 false,所以系统将拒绝对 message1 节点进行读取,即使第一个规则始终为 true 也是如此。

后续步骤

您可以深入了解 Firebase Realtime Database 安全规则:

  • 了解规则语言的下一个主要概念(动态条件),这些条件可让您的规则检查用户授权、比较现有数据和传入数据、验证传入数据、检查来自客户端的查询的结构等等。

  • 查看典型的安全使用场景以及用于解决这些场景的 Firebase 安全规则定义