编写 Cloud Firestore 安全规则的条件

本指南以设计安全规则结构指南为基础,介绍如何向 Cloud Firestore Security Rules添加条件。如果您不熟悉 Cloud Firestore Security Rules 的基础知识,请参阅入门指南。

Cloud Firestore Security Rules 的主要构成要素是条件。条件是一个布尔表达式,用于确定应该允许还是拒绝执行特定操作。使用安全规则可以编写条件来检查用户身份验证情况、验证传入数据,甚至还可访问数据库的其他部分。

身份验证

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

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 Platformrequest.auth 变量包含请求数据的客户端的身份验证信息。如需详细了解 request.auth,请参阅参考文档

数据验证

许多应用都将访问权限控制信息以字段形式存储在数据库中的文档内。 Cloud Firestore Security Rules可以根据文档数据动态地允许或拒绝访问:

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 语句 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';
    }
  }
}

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

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

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

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

Cloud Firestore 安全规则会针对每个查询的潜在结果对其进行评估,如果请求返回客户端无权读取的文档,那么该请求会失败。查询必须遵循安全规则设置的约束。如需详细了解安全规则和查询,请参阅安全地查询数据

后续步骤