获取我们在 Firebase 峰会上发布的所有信息,了解 Firebase 可如何帮助您加快应用开发速度并满怀信心地运行应用。了解详情

Cloud Firestore 安全规则的编写条件

本指南以构建安全规则指南为基础,展示了如何向 Cloud Firestore 安全规则添加条件。如果您不熟悉 Cloud Firestore 安全规则的基础知识,请参阅入门指南。

Cloud Firestore 安全规则的主要构建块是条件。条件是一个布尔表达式,用于确定是允许还是拒绝特定操作。使用安全规则编写检查用户身份验证、验证传入数据甚至访问数据库其他部分的条件。

验证

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

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to access documents in the "cities" collection
    // only if they are authenticated.
    match /cities/{city} {
      allow read, write: if request.auth != null;
    }
  }
}

另一种常见模式是确保用户只能读取和写入他们自己的数据:

service cloud.firestore {
  match /databases/{database}/documents {
    // Make sure the uid of the requesting user matches name of the user
    // document. The wildcard expression {userId} makes the userId variable
    // available in rules.
    match /users/{userId} {
      allow read, update, delete: if request.auth != null && request.auth.uid == userId;
      allow create: if request.auth != null;
    }
  }
}

如果您的应用程序使用 Firebase Authentication 或Google Cloud Identity Platform ,则request.auth变量包含客户端请求数据的身份验证信息。有关request.auth的更多信息,请参阅参考文档

数据验证

许多应用程序将访问控制信息存储为数据库中文档的字段。 Cloud Firestore 安全规则可以根据文档数据动态允许或拒绝访问:

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to read data if the document has the 'visibility'
    // field set to 'public'
    match /cities/{city} {
      allow read: if resource.data.visibility == 'public';
    }
  }
}

resource变量指的是请求的文档, resource.data是存储在文档中的所有字段和值的映射。有关resource变量的更多信息,请参阅参考文档

写入数据时,您可能希望将传入数据与现有数据进行比较。在这种情况下,如果您的规则集允许挂起的写入,则request.resource变量包含文档的未来状态。对于仅修改文档字段子集的update操作, request.resource变量将包含操作后的待定文档状态。您可以检查request.resource中的字段值以防止不需要或不一致的数据更新:

service cloud.firestore {
  match /databases/{database}/documents {
    // Make sure all cities have a positive population and
    // the name is not changed
    match /cities/{city} {
      allow update: if request.resource.data.population > 0
                    && request.resource.data.name == resource.data.name;
    }
  }
}

访问其他文档

使用get()exists()函数,您的安全规则可以根据数据库中的其他文档评估传入请求。 get()exists()函数都需要完全指定的文档路径。当使用变量为get()exists()构造路径时,您需要使用$(variable)语法显式转义变量。

在下面的示例中, database变量由匹配语句match /databases/{database}/documents捕获并用于形成路径:

service cloud.firestore {
  match /databases/{database}/documents {
    match /cities/{city} {
      // Make sure a 'users' document exists for the requesting user before
      // allowing any writes to the 'cities' collection
      allow create: if request.auth != null && exists(/databases/$(database)/documents/users/$(request.auth.uid))

      // Allow the user to delete cities if their user document has the
      // 'admin' field set to 'true'
      allow delete: if request.auth != null && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true
    }
  }
}

对于写入,您可以使用getAfter()函数在事务或批量写入完成之后但在事务或批量提交之前访问文档的状态。与get()一样, getAfter()函数采用完全指定的文档路径。您可以使用getAfter()来定义必须作为事务或批处理一起发生的写入集。

访问通话限制

每个规则集评估的文档访问调用有限制:

  • 10 用于单文档请求和查询请求。
  • 20 用于多文档读取、事务和批量写入。之前的 10 个限制也适用于每个操作。

    例如,假设您创建了一个包含 3 个写入操作的批处理写入请求,并且您的安全规则使用 2 个文档访问调用来验证每个写入。在这种情况下,每个写入使用其 10 个访问调用中的 2 个,而批处理的写入请求使用其 20 个访问调用中的 6 个。

超过任一限制都会导致权限被拒绝错误。某些文档访问调用可能会被缓存,缓存的调用不计入限制。

有关这些限制如何影响事务和批量写入的详细说明,请参阅保护原子操作指南。

访问电话和定价

使用这些函数会在您的数据库中执行读取操作,这意味着即使您的规则拒绝请求,您也会为读取文档付费。有关更具体的结算信息,请参阅Cloud Firestore 定价

自定义函数

随着您的安全规则变得越来越复杂,您可能希望将条件集包装在可以在您的规则集中重复使用的函数中。安全规则支持自定义函数。自定义函数的语法有点像 JavaScript,但安全规则函数是用具有一些重要限制的特定领域语言编写的:

  • 函数只能包含一个return语句。它们不能包含任何附加逻辑。例如,它们不能执行循环或调用外部服务。
  • 函数可以从定义它们的范围内自动访问函数和变量。例如, service cloud.firestore范围内定义的函数可以访问resource变量和内置函数,例如get()exists()
  • 函数可以调用其他函数但不能递归。总调用堆栈深度限制为 10。
  • 在规则版本v2中,函数可以使用let关键字定义变量。函数最多可以有 10 个 let 绑定,但必须以 return 语句结尾。

函数使用function关键字定义,并接受零个或多个参数。例如,您可能希望将上面示例中使用的两种类型的条件组合到一个函数中:

service cloud.firestore {
  match /databases/{database}/documents {
    // True if the user is signed in or the requested data is 'public'
    function signedInOrPublic() {
      return request.auth.uid != null || resource.data.visibility == 'public';
    }

    match /cities/{city} {
      allow read, write: if signedInOrPublic();
    }

    match /users/{user} {
      allow read, write: if signedInOrPublic();
    }
  }
}

随着规则复杂性的增加,在安全规则中使用函数可以使它们更易于维护。

规则不是过滤器

一旦保护了数据并开始编写查询,请记住安全规则不是过滤器。您不能为集合中的所有文档编写查询并期望 Cloud Firestore 仅返回当前客户端有权访问的文档。

例如,采用以下安全规则:

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to read data if the document has the 'visibility'
    // field set to 'public'
    match /cities/{city} {
      allow read: if resource.data.visibility == 'public';
    }
  }
}

拒绝:此规则拒绝以下查询,因为结果集可能包含visibilitypublic的文档:

网络
db.collection("cities").get()
    .then(function(querySnapshot) {
        querySnapshot.forEach(function(doc) {
            console.log(doc.id, " => ", doc.data());
    });
});

Allowed :此规则允许以下查询,因为where("visibility", "==", "public")子句保证结果集满足规则的条件:

网络
db.collection("cities").where("visibility", "==", "public").get()
    .then(function(querySnapshot) {
        querySnapshot.forEach(function(doc) {
            console.log(doc.id, " => ", doc.data());
        });
    });

Cloud Firestore 安全规则根据每个查询的潜在结果评估每个查询,如果它可以返回客户端无权读取的文档,则请求失败。查询必须遵循您的安全规则设置的约束。有关安全规则和查询的更多信息,请参阅安全查询数据

下一步