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

安全规则的工作原理

使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。

安全性可能是应用程序开发难题中最复杂的部分之一。在大多数应用程序中,开发人员必须构建和运行一个服务器来处理身份验证(用户是谁)和授权(用户可以做什么)。

Firebase 安全规则删除了中间(服务器)层,并允许您为直接连接到您的数据的客户端指定基于路径的权限。使用本指南了解有关如何将规则应用于传入请求的更多信息。

选择产品以了解有关其规则的更多信息。

云防火墙

基本结构

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

在构建规则时,理解以下关键概念很重要:

  • 请求:allow语句中调用的一个或多个方法。这些是您允许运行的方法。标准方法是: getlistcreateupdatedeleteread便利方法可以在指定的数据库或存储路径上实现广泛的write访问。
  • 路径:数据库或存储位置,表示为 URI 路径。
  • 规则: allow语句,其中包含一个条件,如果请求的计算结果为 true,则允许请求。

安全规则版本 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变量将解析为城市文档名称,例如SFNYC

匹配子集合

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>;
    }
  }
}

每个 match 语句最多可以有一个递归通配符,但在版本 2 中,您可以将此通配符放在 match 语句中的任何位置。例如:

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()调用的最大数量
  • 10 用于单文档请求和查询请求。
  • 20 用于多文档读取、事务和批量写入。之前的 10 个限制也适用于每个操作。

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

超过任一限制都会导致权限被拒绝错误。

某些文档访问调用可能会被缓存,并且缓存的调用不计入限制。

最大嵌套match语句深度10
一组嵌套match语句中允许的最大路径长度(以路径段为单位) 100
一组嵌套match语句中允许的最大路径捕获变量数20
最大函数调用深度20
函数参数的最大数量7
每个函数的最大let变量绑定数10
递归或循环函数调用的最大数量0(不允许)
每个请求评估的最大表达式数1,000
规则集的最大大小规则集必须遵守两个大小限制:
  • 从 Firebase 控制台或使用firebase deploy从 CLI 发布的规则集文本源的大小限制为 256 KB。
  • 当 Firebase 处理源并使其在后端处于活动状态时,编译规则集的大小限制为 250 KB。

云储存

基本结构

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

在构建规则时,理解以下关键概念很重要:

  • 请求:allow语句中调用的一个或多个方法。这些是您允许运行的方法。标准方法是: getlistcreateupdatedeleteread便利方法可以在指定的数据库或存储路径上实现广泛的write访问。
  • 路径:数据库或存储位置,表示为 URI 路径。
  • 规则: allow语句,其中包含一个条件,如果请求的计算结果为 true,则允许请求。

匹配路径

Cloud Storage 安全规则与用于访问 Cloud Storage 中文件的文件路径相match 。规则可以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

在上述规则中,如果conditionother_condition评估为 true,则可以读取文件“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对象还在request.auth对象中包含用户的唯一 ID 和 Firebase 身份验证负载,这将在文档的身份验证部分中进一步解释。

request对象中的完整属性列表如下:

财产类型描述
auth地图<字符串,字符串>当用户登录时,提供uid (用户的唯一 ID)和token (Firebase Authentication JWT 声明的映射)。否则,它将为null
params地图<字符串,字符串>包含请求的查询参数的映射。
path小路表示正在执行请求的path的路径。
resource地图<字符串,字符串>新资源值,仅在write请求时出现。
time时间戳表示请求被评估的服务器时间的时间戳。

资源评估

在评估规则时,您可能还需要评估正在上传、下载、修改或删除的文件的元数据。这允许您创建复杂而强大的规则,例如只允许上传具有特定内容类型的文件,或者只允许删除大于特定大小的文件。

Cloud Storage 的 Firebase 安全规则在resource对象中提供文件元数据,其中包含 Cloud Storage 对象中出现的元数据的键/值对。可以在readwrite请求时检查这些属性,以确保数据完整性。

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包含所有这些,但generationmetagenerationetagtimeCreatedupdated除外。

安全规则限制

使用安全规则时,请注意以下限制:

限制细节
每个请求的最大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 结构。
  • 请求:这些是规则用于授予访问权限的方法。 readwrite规则授予广泛的读取和写入访问权限,而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
});
Objective-C
注意:此 Firebase 产品不适用于 App Clip 目标。
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
}];
迅速
注意:此 Firebase 产品不适用于 App Clip 目标。
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
});
Objective-C
注意:此 Firebase 产品不适用于 App Clip 目标。
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
[[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
    // SUCCESS!
}];
迅速
注意:此 Firebase 产品不适用于 App Clip 目标。
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 }
      }
    }
  }