安全性可能是应用程序开发难题中最复杂的部分之一。在大多数应用程序中,开发人员必须构建并运行一个服务器来处理身份验证(用户是谁)和授权(用户可以做什么)。
Firebase 安全规则删除了中间(服务器)层,并允许您为直接连接到您的数据的客户端指定基于路径的权限。使用本指南了解有关如何将规则应用于传入请求的更多信息。
选择一个产品以了解有关其规则的更多信息。
云端 Firestore
基本结构
Cloud Firestore 和 Cloud Storage 中的 Firebase 安全规则使用以下结构和语法:
service <<name>> {
// Match the resource path.
match <<path>> {
// Allow the request if the following conditions are true.
allow <<methods>> : if <<condition>>
}
}
在构建规则时,了解以下关键概念很重要:
- Request:在
allow
语句中调用的一个或多个方法。这些是您允许运行的方法。标准方法是:get
、list
、create
、update
和delete
。read
和write
便利方法支持对指定数据库或存储路径进行广泛的读取和写入访问。 - 路径:数据库或存储位置,表示为 URI 路径。
- 规则:
allow
语句,其中包含一个条件,如果它的计算结果为真,则允许请求。
安全规则版本 2
自 2019 年 5 月起,Firebase 安全规则的第 2 版现已可用。规则的版本 2 更改了递归通配符{name=**}
的行为。如果您计划使用集合组查询,则必须使用版本 2。您必须通过rules_version = '2';
安全规则的第一行:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
匹配路径
所有匹配语句都应指向文档,而不是集合。 match 语句可以指向特定文档,如match /cities/SF
或使用通配符指向指定路径中的任何文档,如match /cities/{city}
。
在上面的示例中,匹配语句使用{city}
通配符语法。这意味着该规则适用于cities
集合中的任何文档,例如/cities/SF
或/cities/NYC
。当评估 match 语句中的allow
表达式时, city
变量将解析为城市文档名称,例如SF
或NYC
。
匹配子集合
Cloud Firestore 中的数据被组织成文档集合,每个文档都可以通过子集合扩展层次结构。了解安全规则如何与分层数据交互很重要。
考虑cities
集合中的每个文档都包含一个landmarks
子集合的情况。安全规则仅适用于匹配的路径,因此在cities
集合上定义的访问控制不适用于landmarks
子集合。相反,编写明确的规则来控制对子集合的访问:
service cloud.firestore {
match /databases/{database}/documents {
match /cities/{city} {
allow read, write: if <condition>;
// Explicitly define rules for the 'landmarks' subcollection
match /landmarks/{landmark} {
allow read, write: if <condition>;
}
}
}
}
嵌套match
语句时,内层match
语句的路径总是相对于外层match
语句的路径。因此,以下规则集是等效的:
service cloud.firestore {
match /databases/{database}/documents {
match /cities/{city} {
match /landmarks/{landmark} {
allow read, write: if <condition>;
}
}
}
}
service cloud.firestore {
match /databases/{database}/documents {
match /cities/{city}/landmarks/{landmark} {
allow read, write: if <condition>;
}
}
}
递归通配符
如果您希望规则适用于任意深度的层次结构,请使用递归通配符语法{name=**}
:
service cloud.firestore {
match /databases/{database}/documents {
// Matches any document in the cities collection as well as any document
// in a subcollection.
match /cities/{document=**} {
allow read, write: if <condition>;
}
}
}
使用递归通配符语法时,通配符变量将包含整个匹配路径段,即使文档位于深度嵌套的子集合中。例如,上面列出的规则将匹配位于/cities/SF/landmarks/coit_tower
的文档, document
变量的值将为SF/landmarks/coit_tower
。
但是请注意,递归通配符的行为取决于规则版本。
版本 1
默认情况下,安全规则使用版本 1。在版本 1 中,递归通配符匹配一个或多个路径项。它们不匹配空路径,因此match /cities/{city}/{document=**}
匹配子集合中的文档但不匹配cities
集合中的文档,而match /cities/{document=**}
匹配子集合中的两个文档cities
集合和子集合。
递归通配符必须出现在匹配语句的末尾。
版本 2
在安全规则的版本 2 中,递归通配符匹配零个或多个路径项。 match/cities/{city}/{document=**}
匹配任何子集合中的文档以及cities
集合中的文档。
您必须通过添加rules_version = '2';
在您的安全规则的顶部:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Matches any document in the cities collection as well as any document
// in a subcollection.
match /cities/{city}/{document=**} {
allow read, write: if <condition>;
}
}
}
每个匹配语句最多可以有一个递归通配符,但在版本 2 中,您可以将此通配符放在匹配语句中的任何位置。例如:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Matches any document in the songs collection group
match /{path=**}/songs/{song} {
allow read, write: if <condition>;
}
}
}
如果您使用集合组查询,则必须使用版本 2,请参阅保护集合组查询。
重叠匹配语句
一个文档可能匹配多个match
语句。在多个allow
表达式匹配请求的情况下,如果任何条件为true
,则允许访问:
service cloud.firestore {
match /databases/{database}/documents {
// Matches any document in the 'cities' collection.
match /cities/{city} {
allow read, write: if false;
}
// Matches any document in the 'cities' collection or subcollections.
match /cities/{document=**} {
allow read, write: if true;
}
}
}
在上面的示例中,将允许对cities
集合的所有读写操作,因为第二条规则始终为true
,即使第一条规则始终为false
。
安全规则限制
在使用安全规则时,请注意以下限制:
限制 | 细节 |
---|---|
每个请求的exists() 、 get() 和getAfter() 调用的最大次数 |
超过任一限制都会导致权限被拒绝错误。 某些文档访问调用可能会被缓存,缓存的调用不计入限制。 |
最大嵌套match 语句深度 | 10 |
一组嵌套match 语句中允许的最大路径长度(以路径段为单位) | 100 |
一组嵌套match 语句中允许的最大路径捕获变量数 | 20 |
最大函数调用深度 | 20 |
函数参数的最大数量 | 7 |
每个函数的最大let 变量绑定数 | 10 |
递归或循环函数调用的最大次数 | 0(不允许) |
每个请求评估的最大表达式数 | 1,000 |
规则集的最大大小 | 规则集必须遵守两个大小限制:
|
云储存
基本结构
Cloud Firestore 和 Cloud Storage 中的 Firebase 安全规则使用以下结构和语法:
service <<name>> {
// Match the resource path.
match <<path>> {
// Allow the request if the following conditions are true.
allow <<methods>> : if <<condition>>
}
}
在构建规则时,了解以下关键概念很重要:
- Request:在
allow
语句中调用的一个或多个方法。这些是您允许运行的方法。标准方法是:get
、list
、create
、update
和delete
。read
和write
便利方法支持对指定数据库或存储路径进行广泛的读取和写入访问。 - 路径:数据库或存储位置,表示为 URI 路径。
- 规则:
allow
语句,其中包含一个条件,如果它的计算结果为真,则允许请求。
匹配路径
Cloud Storage 安全规则match
用于访问 Cloud Storage 中文件的文件路径。规则可以match
精确路径或通配符路径,规则也可以嵌套。如果没有匹配规则允许请求方法,或者条件评估为false
,则请求被拒绝。
完全匹配
// Exact match for "images/profilePhoto.png" match /images/profilePhoto.png { allow write: if <condition>; } // Exact match for "images/croppedProfilePhoto.png" match /images/croppedProfilePhoto.png { allow write: if <other_condition>; }
嵌套匹配
// Partial match for files that start with "images" match /images { // Exact match for "images/profilePhoto.png" match /profilePhoto.png { allow write: if <condition>; } // Exact match for "images/croppedProfilePhoto.png" match /croppedProfilePhoto.png { allow write: if <other_condition>; } }
通配符比赛
规则也可用于使用通配符来match
模式。通配符是一个命名变量,表示单个字符串(例如profilePhoto.png
)或多个路径段(例如images/profilePhoto.png
)。
通过在通配符名称周围添加花括号来创建通配符,例如{string}
。可以通过将=**
添加到通配符名称来声明多段通配符,例如{path=**}
:
// Partial match for files that start with "images" match /images { // Exact match for "images/*" // e.g. images/profilePhoto.png is matched match /{imageId} { // This rule only matches a single path segment (*) // imageId is a string that contains the specific segment matched allow read: if <condition>; } // Exact match for "images/**" // e.g. images/users/user:12345/profilePhoto.png is matched // images/profilePhoto.png is also matched! match /{allImages=**} { // This rule matches one or more path segments (**) // allImages is a path that contains all segments matched allow read: if <other_condition>; } }
如果多个规则匹配一个文件,则结果是所有规则评估结果的OR
。也就是说,如果文件匹配的任何规则评估为true
,则结果为true
。
在上面的规则中,如果condition
或other_condition
的计算结果为真,则可以读取文件“images/profilePhoto.png”,而文件“images/users/user:12345/profilePhoto.png”仅受other_condition
的结果影响.
可以从match
中引用通配符变量提供文件名或路径授权:
// Another way to restrict the name of a file match /images/{imageId} { allow read: if imageId == "profilePhoto.png"; }
云存储安全规则不会级联,只有当请求路径与指定规则的路径匹配时才会评估规则。
请求评估
使用发送到 Cloud Storage 的request
评估上传、下载、元数据更改和删除。 request
变量包含执行请求的文件路径、收到请求的时间以及新的resource
值(如果请求是写入)。还包括 HTTP 标头和身份验证状态。
request
对象还包含用户的唯一 ID 和request.auth
对象中的 Firebase 身份验证有效负载,这将在文档的身份验证部分中进一步解释。
request
对象中的完整属性列表如下:
财产 | 类型 | 描述 |
---|---|---|
auth | 地图<字符串,字符串> | 当用户登录时,提供用户的唯一 ID uid 和token ,即 Firebase 身份验证 JWT 声明的映射。否则,它将为null 。 |
params | 地图<字符串,字符串> | 包含请求的查询参数的映射。 |
path | 小路 | 表示正在执行请求的path 的路径。 |
resource | 地图<字符串,字符串> | 新的资源值,仅出现在write 请求中。 |
time | 时间戳 | 表示请求被评估的服务器时间的时间戳。 |
资源评价
在评估规则时,您可能还想评估正在上传、下载、修改或删除的文件的元数据。这使您可以创建复杂而强大的规则,例如只允许上传具有特定内容类型的文件,或者只允许删除大于特定大小的文件。
Cloud Storage 的 Firebase 安全规则在resource
对象中提供文件元数据,其中包含 Cloud Storage 对象中显示的元数据的键/值对。可以在read
或write
请求时检查这些属性以确保数据完整性。
在write
请求(例如上传、元数据更新和删除)时,除了包含请求路径中当前存在的文件的文件元数据的resource
对象之外,您还可以使用request.resource
对象,如果允许写入,它包含要写入的文件元数据的子集。您可以使用这两个值来确保数据完整性或实施应用程序约束,例如文件类型或大小。
下面提供了resource
对象中的完整属性列表:
财产 | 类型 | 描述 |
---|---|---|
name | 细绳 | 对象的全名 |
bucket | 细绳 | 此对象所在的存储桶的名称。 |
generation | 整数 | 此对象的Google Cloud Storage 对象生成。 |
metageneration | 整数 | 此对象的Google Cloud Storage 对象元生成。 |
size | 整数 | 对象的大小(以字节为单位)。 |
timeCreated | 时间戳 | 表示对象创建时间的时间戳。 |
updated | 时间戳 | 表示对象上次更新时间的时间戳。 |
md5Hash | 细绳 | 对象的 MD5 散列。 |
crc32c | 细绳 | 对象的 crc32c 散列。 |
etag | 细绳 | 与此对象关联的 etag。 |
contentDisposition | 细绳 | 与此对象关联的内容配置。 |
contentEncoding | 细绳 | 与此对象关联的内容编码。 |
contentLanguage | 细绳 | 与此对象关联的内容语言。 |
contentType | 细绳 | 与此对象关联的内容类型。 |
metadata | 地图<字符串,字符串> | 额外的键/值对,开发人员指定的自定义元数据。 |
request.resource
包含所有这些,但generation
、 metageneration
、 etag
、 timeCreated
和updated
除外。
安全规则限制
在使用安全规则时,请注意以下限制:
限制 | 细节 |
---|---|
每个请求的最大firestore.exists() 和firestore.get() 调用次数 | 2 用于单文档请求和查询请求。 超过此限制会导致权限被拒绝错误。 对相同文档的访问调用可能会被缓存,缓存的调用不计入限制。 |
完整示例
综上所述,您可以为图像存储解决方案创建一个完整的规则示例:
service firebase.storage { match /b/{bucket}/o { match /images { // Cascade read to any image type at any path match /{allImages=**} { allow read; } // Allow write files to the path "images/*", subject to the constraints: // 1) File is less than 5MB // 2) Content type is an image // 3) Uploaded content type matches existing content type // 4) File name (stored in imageId wildcard variable) is less than 32 characters match /{imageId} { allow write: if request.resource.size < 5 * 1024 * 1024 && request.resource.contentType.matches('image/.*') && request.resource.contentType == resource.contentType && imageId.size() < 32 } } } }
实时数据库
基本结构
在实时数据库中,Firebase 安全规则由 JSON 文档中包含的类似 JavaScript 的表达式组成。
他们使用以下语法:
{
"rules": {
"<<path>>": {
// Allow the request if the condition for each method is true.
".read": <<condition>>,
".write": <<condition>>,
".validate": <<condition>>
}
}
}
该规则包含三个基本要素:
- 路径:数据库位置。这反映了数据库的 JSON 结构。
- 请求:这些是规则用于授予访问权限的方法。
read
和write
规则授予广泛的读取和写入访问权限,而validate
规则充当辅助验证以根据传入或现有数据授予访问权限。 - 条件:如果计算结果为真,则允许请求的条件。
规则如何应用于路径
在实时数据库中,规则以原子方式应用,这意味着更高级别父节点的规则会覆盖更细粒度的子节点的规则,而更深层次节点的规则无法授予对父路径的访问权限。如果您已经为其中一个父路径授予访问权限,则无法优化或撤销对数据库结构中更深路径的访问权限。
考虑以下规则:
{ "rules": { "foo": { // allows read to /foo/* ".read": "data.child('baz').val() === true", "bar": { // ignored, since read was allowed already ".read": false } } } }
此安全结构允许在/foo/
包含值为true
的子baz
时读取/bar/
。 /foo/bar/
下的".read": false
规则在这里无效,因为访问不能被子路径撤销。
虽然它可能看起来并不直观,但它是规则语言的强大部分,允许以最小的努力实现非常复杂的访问权限。这对于基于用户的安全性特别有用。
但是, .validate
规则不会级联。必须满足层次结构的所有级别的所有验证规则,才能允许写入。
此外,由于规则不适用于父路径,如果在请求的位置或授予访问权限的父位置没有规则,则读取或写入操作将失败。即使每个受影响的子路径都可以访问,在父位置的读取也会完全失败。考虑这个结构:
{ "rules": { "records": { "rec1": { ".read": true }, "rec2": { ".read": false } } } }
如果不理解规则是自动评估的,那么获取/records/
路径似乎会返回rec1
而不是rec2
。然而,实际结果是错误的:
JavaScript
var db = firebase.database(); db.ref("records").once("value", function(snap) { // success method is not called }, function(err) { // error callback triggered with PERMISSION_DENIED });
目标-C
FIRDatabaseReference *ref = [[FIRDatabase database] reference]; [[_ref child:@"records"] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) { // success block is not called } withCancelBlock:^(NSError * _Nonnull error) { // cancel block triggered with PERMISSION_DENIED }];
迅速
var ref = FIRDatabase.database().reference() ref.child("records").observeSingleEventOfType(.Value, withBlock: { snapshot in // success block is not called }, withCancelBlock: { error in // cancel block triggered with PERMISSION_DENIED })
爪哇
FirebaseDatabase database = FirebaseDatabase.getInstance(); DatabaseReference ref = database.getReference("records"); ref.addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot snapshot) { // success method is not called } @Override public void onCancelled(FirebaseError firebaseError) { // error callback triggered with PERMISSION_DENIED }); });
休息
curl https://docs-examples.firebaseio.com/rest/records/ # response returns a PERMISSION_DENIED error
由于/records/
的读取操作是原子的,并且没有读取规则授予对/records/
下所有数据的访问权限,因此这将引发PERMISSION_DENIED
错误。如果我们在Firebase 控制台的安全模拟器中评估此规则,我们可以看到读取操作被拒绝:
Attempt to read /records with auth=Success(null) / /records No .read rule allowed the operation. Read was denied.
该操作被拒绝,因为没有读取规则允许访问/records/
路径,但请注意, rec1
的规则从未被评估,因为它不在我们请求的路径中。要获取rec1
,我们需要直接访问它:
JavaScript
var db = firebase.database(); db.ref("records/rec1").once("value", function(snap) { // SUCCESS! }, function(err) { // error callback is not called });
目标-C
FIRDatabaseReference *ref = [[FIRDatabase database] reference]; [[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) { // SUCCESS! }];
迅速
var ref = FIRDatabase.database().reference() ref.child("records/rec1").observeSingleEventOfType(.Value, withBlock: { snapshot in // SUCCESS! })
爪哇
FirebaseDatabase database = FirebaseDatabase.getInstance(); DatabaseReference ref = database.getReference("records/rec1"); ref.addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot snapshot) { // SUCCESS! } @Override public void onCancelled(FirebaseError firebaseError) { // error callback is not called } });
休息
curl https://docs-examples.firebaseio.com/rest/records/rec1 # SUCCESS!
位置变量
实时数据库规则支持$location
变量来匹配路径段。在你的路径段前面使用$
前缀来匹配你的规则到路径上的任何子节点。
{
"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')"
}
}
}
}
}
您还可以将$variable
与常量路径名并行使用。
{
"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 }
}
}
}