本指南以学习核心 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
除了title
和color
之外没有其他孩子。任何会导致创建额外子项的写入都将失败。
{ "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 实时数据库集成,允许您使用条件控制每个用户的数据访问。用户通过身份验证后,Realtime Database Security Rules 规则中的auth
变量将填充用户信息。此信息包括他们的唯一标识符 ( uid
) 以及链接的帐户数据,例如 Facebook id 或电子邮件地址,以及其他信息。如果您实施自定义身份验证提供程序,则可以将您自己的字段添加到用户的身份验证有效负载中。
本节介绍如何将 Firebase 实时数据库安全规则语言与有关用户的身份验证信息相结合。通过结合这两个概念,您可以根据用户身份控制对数据的访问。
auth
变量
在进行身份验证之前,规则中预定义的auth
变量为 null。
使用Firebase 身份验证对用户进行身份验证后,它将包含以下属性:
供应商 | 使用的身份验证方法(“密码”、“匿名”、“facebook”、“github”、“google”或“twitter”)。 |
uid | 唯一的用户 ID,保证在所有提供商中都是唯一的。 |
令牌 | Firebase Auth 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()"
引用其他路径中的数据
任何数据都可以用作规则的标准。使用预定义变量root
、 data
和newData
,我们可以访问写入事件之前或之后存在的任何路径。
考虑这个例子,它允许写操作,只要/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);
目标-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.
表达式在实时数据库安全规则中可用。
基于查询的规则表达式 | ||
---|---|---|
表达 | 类型 | 描述 |
查询.orderByKey 查询.orderByPriority 查询.orderByValue | 布尔值 | 对于按键、优先级或值排序的查询为真。否则为假。 |
query.orderByChild | 细绳 无效的 | 使用字符串表示子节点的相对路径。例如, query.orderByChild === "address/zip" 。如果查询未按子节点排序,则此值为空。 |
查询.startAt 查询.endAt 查询.等于 | 细绳 数字 布尔值 无效的 | 检索执行查询的边界,如果没有边界集则返回 null。 |
查询.limitToFirst 查询.limitToLast | 数字 无效的 | 检索执行查询的限制,如果未设置限制,则返回 null。 |
下一步
在讨论了条件之后,您对规则有了更深入的了解,并准备好:
了解如何处理核心用例,并了解开发、测试和部署规则的工作流程:
- 了解可用于构建条件的全套预定义规则变量。
- 编写解决常见情况的规则。
- 通过回顾您必须发现和避免不安全规则的情况来积累您的知识。
- 了解 Firebase Local Emulator Suite 以及如何使用它来测试规则。
- 查看可用于部署规则的方法。
学习特定于实时数据库的规则功能:
- 了解如何为实时数据库编制索引。
- 查看用于部署规则的 REST API 。