Google 致力于为黑人社区推动种族平等。查看具体举措
此页面由 Cloud Translation API 翻译。
Switch to English

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

本指南以学习核心Firebase安全规则语言指南为基础,以显示如何向Firebase实时数据库安全规则添加条件。

条件是实时数据库安全规则的主要构建块。条件是一个布尔表达式,它确定是应允许还是拒绝特定操作。对于基本规则,使用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变量将使用用户的信息进行填充。此信息包括其唯一标识符( 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()"

在其他路径中引用数据

任何数据都可以用作规则的标准。使用预定义的变量rootdatanewData ,我们可以访问写入事件之前或之后存在的任何路径。

考虑以下示例,只要/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。

下一步

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

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

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