本指南以学习核心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实时数据库集成在一起,可让您使用条件控制每个用户的数据访问。用户认证后,“实时数据库安全规则”规则中的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()"
在其他路径中引用数据
任何数据都可以用作规则的标准。使用预定义的变量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
规则成功授予访问权限之后才可以运行.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.
实时数据库安全规则中提供了表达式。
基于查询的规则表达式 | ||
---|---|---|
表达 | 类型 | 描述 |
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。 |
下一步
在讨论条件之后,您将对规则有更深入的了解,并准备:
了解如何处理核心用例,并了解用于开发,测试和部署规则的工作流:
- 了解可用于构建条件的全套预定义的Rule变量。
- 编写解决常见情况的规则。
- 通过回顾必须发现和避免不安全规则的情况来建立自己的知识。
- 了解有关Firebase本地仿真器套件以及如何使用它测试规则的信息。
- 查看可用于部署规则的方法。
特定于实时数据库的学习规则功能: