Безопасность может быть одним из самых сложных аспектов разработки приложения. В большинстве приложений разработчикам приходится создавать и запускать сервер, который отвечает за аутентификацию (кто пользователь) и авторизацию (что пользователь может делать).
Firebase Security Rules удаляют промежуточный (серверный) уровень и позволяют задавать разрешения на основе пути для клиентов, подключающихся к вашим данным напрямую. Используйте это руководство, чтобы узнать больше о применении правил к входящим запросам.
Выберите продукт, чтобы узнать больше о его правилах.
Cloud Firestore
Базовая структура
Firebase Security Rules в Cloud Firestore и Cloud Storage используют следующую структуру и синтаксис:
service <<name>> {
// Match the resource path.
match <<path>> {
// Allow the request if the following conditions are true.
allow <<methods>> : if <<condition>>
}
}
При разработке правил важно понимать следующие ключевые концепции:
- Запрос: Метод или методы, вызываемые в операторе
allow
. Это методы, запуск которых вы разрешаете. Стандартные методы:get
,list
,create
,update
иdelete
. Вспомогательные методыread
иwrite
обеспечивают широкий доступ для чтения и записи к указанной базе данных или пути к хранилищу. - Путь: база данных или место хранения, представленное в виде пути URI.
- Правило:
allow
оператор, включающий условие, разрешающее запрос, если оно оценивается как истинное.
Правила безопасности версии 2
С мая 2019 года доступна версия 2 правил безопасности Firebase . Версия 2 изменяет поведение рекурсивных подстановочных знаков {name=**}
. Если вы планируете использовать запросы к группам коллекций , необходимо использовать версию 2. Необходимо включить версию 2, указав rules_version = '2';
в первой строке правил безопасности:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
Сопоставление путей
Все операторы сопоставления должны указывать на документы, а не на коллекции. Оператор сопоставления может указывать на конкретный документ, например, match /cities/SF
, или использовать подстановочные знаки для указания на любой документ по указанному пути, например, match /cities/{city}
.
В этом примере оператор match использует подстановочный синтаксис {city}
. Это означает, что правило применяется к любому документу из коллекции cities
, например, /cities/SF
или /cities/NYC
. При вычислении выражений allow
в операторе match переменная 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>;
}
}
}
Перекрывающиеся заявления о совпадении
Документ может соответствовать нескольким выражениям 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.
match /cities/{document} {
allow read, write: if true;
}
}
}
В этом примере все операции чтения и записи в коллекции cities
будут разрешены, поскольку второе правило всегда true
, даже если первое правило всегда false
.
Рекурсивные подстановочные знаки
Если вы хотите, чтобы правила применялись к произвольно глубокой иерархии, используйте рекурсивный синтаксис подстановочных знаков, {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
.
Вам необходимо включить версию 2, добавив 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, см . раздел Защита запросов группы коллекций .
Ограничения правил безопасности
При работе с правилами безопасности обратите внимание на следующие ограничения:
Предел | Подробности |
---|---|
Максимальное количество вызовов exists() , get() и getAfter() на запрос |
Превышение любого из этих ограничений приведет к ошибке отказа в доступе. Некоторые вызовы доступа к документам могут кэшироваться, а кэшированные вызовы не учитываются при расчете ограничений. |
Максимальная глубина вложенного оператора match | 10 |
Максимальная длина пути (в сегментах пути), допустимая в пределах набора вложенных операторов match | 100 |
Максимальное количество переменных захвата пути, разрешенное в наборе вложенных операторов match | 20 |
Максимальная глубина вызова функции | 20 |
Максимальное количество аргументов функции | 7 |
Максимальное количество привязок переменных let на функцию | 10 |
Максимальное количество рекурсивных или циклических вызовов функций | 0 (не разрешено) |
Максимальное количество выражений, оцениваемых за запрос | 1000 |
Максимальный размер набора правил | Наборы правил должны подчиняться двум ограничениям по размеру:
|
Cloud Storage
Базовая структура
Firebase Security Rules в Cloud Firestore и Cloud Storage используют следующую структуру и синтаксис:
service <<name>> {
// Match the resource path.
match <<path>> {
// Allow the request if the following conditions are true.
allow <<methods>> : if <<condition>>
}
}
При разработке правил важно понимать следующие ключевые концепции:
- Запрос: Метод или методы, вызываемые в операторе
allow
. Это методы, запуск которых вы разрешаете. Стандартные методы:get
,list
,create
,update
иdelete
. Вспомогательные методыread
иwrite
обеспечивают широкий доступ для чтения и записи к указанной базе данных или пути к хранилищу. - Путь: база данных или место хранения, представленное в виде пути URI.
- Правило:
allow
оператор, включающий условие, разрешающее запрос, если оно оценивается как истинное.
Сопоставление путей
Cloud Storage Security Rules 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
.
В правилах файл «images/profilePhoto.png» может быть прочитан, если одно из condition
или other_condition
имеет значение true, тогда как файл «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 Security Rules не каскадируются, и правила оцениваются только в том случае, если путь запроса совпадает с путем, указанным в правилах.
Запросить оценку
Загрузка, скачивание, изменение метаданных и удаление данных оцениваются с помощью request
, отправляемого в Cloud Storage . Переменная request
содержит путь, по которому выполняется запрос, время получения запроса и новое значение resource
, если запрос — запись. Также включаются HTTP-заголовки и состояние аутентификации.
Объект request
также содержит уникальный идентификатор пользователя и полезную нагрузку Firebase Authentication в объекте request.auth
, что будет более подробно описано в разделе «Аутентификация» документации.
Полный список свойств объекта request
доступен ниже:
Свойство | Тип | Описание |
---|---|---|
auth | карта<строка, строка> | При входе пользователя в систему указывается uid (уникальный идентификатор пользователя) и token (карта утверждений Firebase Authentication JWT). В противном случае возвращается значение null . |
params | карта<строка, строка> | Карта, содержащая параметры запроса. |
path | путь | path представляющий путь, по которому выполняется запрос. |
resource | карта<строка, строка> | Новое значение ресурса, присутствующее только в запросах write . |
time | метка времени | Метка времени, представляющая время сервера, когда оценивается запрос. |
Оценка ресурсов
При оценке правил вы также можете оценить метаданные загружаемого, скачиваемого, изменяемого или удаляемого файла. Это позволяет создавать сложные и эффективные правила, которые, например, разрешают загрузку только файлов с определённым типом содержимого или удаление только файлов, размер которых превышает определённый.
Firebase Security Rules для Cloud Storage предоставляют метаданные файла в объекте 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 { // 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) Filename (stored in imageId wildcard variable) is less than 32 characters match /{imageId} { allow read; allow write: if request.resource.size < 5 * 1024 * 1024 && request.resource.contentType.matches('image/.*') && request.resource.contentType == resource.contentType && imageId.size() < 32 } } } }
Realtime Database
Базовая структура
В Realtime Database Firebase Security Rules состоят из выражений, подобных JavaScript, содержащихся в документе JSON.
Они используют следующий синтаксис:
{
"rules": {
"<<path>>": {
// Allow the request if the condition for each method is true.
".read": <<condition>>,
".write": <<condition>>,
".validate": <<condition>>
}
}
}
Правило содержит три основных элемента:
- Путь: расположение базы данных. Он отражает JSON-структуру вашей базы данных.
- Запрос: это методы, которые правило использует для предоставления доступа. Правила
read
иwrite
предоставляют широкий доступ на чтение и запись, в то время как правилаvalidate
выполняют функцию вторичной проверки для предоставления доступа на основе входящих или существующих данных. - Условие: Условие, разрешающее запрос, если оно оценивается как истинное.
Как правила применяются к путям
В Realtime Database Rules применяются атомарно, то есть правила на родительских узлах более высокого уровня переопределяют правила на дочерних узлах более высокого уровня, а правила на более глубоком узле не могут предоставить доступ к родительскому пути. Вы не можете уточнить или отозвать доступ к более глубокому пути в структуре базы данных, если вы уже предоставили его для одного из родительских путей.
Примите во внимание следующие правила:
{ "rules": { "foo": { // allows read to /foo/* ".read": "data.child('baz').val() === true", "bar": { // ignored, since read was allowed already ".read": false } } } }
Эта структура безопасности позволяет читать из /bar/
, если /foo/
содержит дочерний baz
со значением true
. Правило ".read": false
в /foo/bar/
здесь не действует, поскольку доступ не может быть отозван дочерним путем.
Хотя это может показаться не совсем интуитивно понятным, это мощная часть языка правил, позволяющая реализовать очень сложные права доступа с минимальными усилиями. Это особенно полезно для обеспечения безопасности на уровне пользователей .
Однако правила .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
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 });
Objective-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!
Переменная местоположения
Rules Realtime Database поддерживают переменную $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 }
}
}
}