Join us for Firebase Summit on November 10, 2021. Tune in to learn how Firebase can help you accelerate app development, release with confidence, and scale with ease. Register

实时数据库规则中的使用条件

该指南基于学习的核心火力地堡安全规则的语言指南,说明如何条件添加到您的火力地堡实时数据库安全规则。

实时数据库安全规则的主要组成部分是条件。条件是一个布尔表达式,用于确定应允许还是拒绝特定操作。对于基本规则,使用truefalse文字作为条件工作prefectly很好。但是实时数据库安全规则语言为您提供了编写更复杂条件的方法,这些条件可以:

  • 检查用户身份验证
  • 根据新提交的数据评估现有数据
  • 访问和比较数据库的不同部分
  • 验证传入数据
  • 将传入查询的结构用于安全逻辑

使用 $ 变量捕获路径段

您可以通过声明捕获变量与捕捉读或写路径的部分$前缀。这用作通配符,并存储该键的值以供在规则条件内使用:

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

验证

最常见的安全规则模式之一是根据用户的身份验证状态控制访问。例如,您的应用可能希望仅允许登录用户写入数据。

如果应用程序使用火力地堡身份验证,则request.auth变量包含客户端请求数据的验证信息。有关更多信息request.auth ,请参阅参考文档

Firebase 身份验证与 Firebase 实时数据库集成,允许您使用条件控制每个用户的数据访问。一旦用户通过验证,该auth在你的实时数据库安全规则的规则变量将用户的信息填充。此信息包括其唯一标识符( uid )以及关联的帐户数据,诸如Facebook的ID或电子邮件地址,以及其他信息。如果您实现自定义身份验证提供程序,则可以将自己的字段添加到用户的身份验证负载中。

本部分介绍了如何将 Firebase 实时数据库安全规则语言与用户的身份验证信息结合起来。通过结合这两个概念,您可以根据用户身份控制对数据的访问。

auth变量

预定义的auth的规则变量为空认证发生之前。

一旦用户与认证火力地堡验证它包含以下属性:

提供者使用的身份验证方法(“密码”、“匿名”、“facebook”、“github”、“google”或“twitter”)。
用户界面唯一的用户 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"
      }
    }
  }
}

使用身份验证自定义声明

对于需要针对不同的用户自定义访问控制应用,火力地堡认证允许开发者在火力地堡用户组声明。这些说法是在入店auth.token规则中的变量。这里的规则,利用的例子hasEmergencyTowel定制要求:

{
  "rules": {
    "frood": {
      // A towel is about the most massively useful thing an interstellar
      // hitchhiker can have
      ".read": "auth.token.hasEmergencyTowel === true"
    }
  }
}

开发人员创建自己的定制身份验证令牌可以选择添加声称这些令牌。这些说法是对avaialble 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);
目标-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
布尔值对于按键、优先级或值排序的查询为真。否则为假。
query.orderByChild细绳
空值
使用字符串表示子节点的相对路径。例如, query.orderByChild == "address/zip"如果查询不是按子节点排序,则此值为空。
query.startAt
查询.endAt
查询.equalTo
细绳
数字
布尔值
空值
检索正在执行的查询的边界,如果没有设置边界,则返回 null。
query.limitToFirst
query.limitToLast
数字
空值
检索正在执行的查询的限制,如果没有设置限制,则返回 null。

下一步

在对条件的讨论之后,您对规则有了更深入的了解,并准备好:

了解如何处理核心用例,并了解开发、测试和部署规则的工作流程:

学习特定于实时数据库的规则功能: