Безопасность может быть одной из самых сложных частей головоломки разработки приложений. В большинстве приложений разработчикам приходится создавать и запускать сервер, который обрабатывает аутентификацию (кто пользователь) и авторизацию (что может делать пользователь).
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 может указывать на конкретный документ, как в match /cities/SF
или использовать подстановочные знаки для указания на любой документ по указанному пути, как в match /cities/{city}
.
В приведенном выше примере оператор сопоставления использует синтаксис подстановочного знака {city}
. Это означает, что правило применяется к любому документу в коллекции cities
, например /cities/SF
или /cities/NYC
. При оценке 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
.
Вы должны подписаться на версию 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, см. раздел «Защита запросов группы сбора» .
Перекрывающиеся операторы сопоставления
Документ может соответствовать более чем одному оператору 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 (не разрешено) |
Максимальное количество выражений, оцениваемых за запрос | 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 , карту утверждений JWT Firebase Authentication . В противном случае оно будет 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 | нить | Этаг, связанный с этим объектом. |
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 } } } }
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 });
Цель-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!
Переменная местоположения
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 }
}
}
}