Как работают правила безопасности

Безопасность может быть одной из самых сложных составляющих процесса разработки приложений. В большинстве приложений разработчикам приходится создавать и запускать сервер, который обрабатывает аутентификацию (кто является пользователем) и авторизацию (что пользователь может делать).

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 может указывать на конкретный документ, например, 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>;
    }
  }
}

В одном операторе match можно использовать не более одного рекурсивного подстановочного знака, но во второй версии этот подстановочный знак можно разместить в любом месте оператора 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; см. раздел «Защита запросов к группам коллекций» .

Ограничения правил безопасности

При работе с правилами безопасности учитывайте следующие ограничения:

Лимит Подробности
Максимальное количество вызовов методов exists() , get() и getAfter() за один запрос.
  • 10 для запросов отдельных документов и запросов запросов.
  • 20 для чтения нескольких документов, транзакций и пакетной записи. Предыдущее ограничение в 10 также применяется к каждой операции.

    Например, представьте, что вы создаете пакетный запрос на запись с 3 операциями записи, и ваши правила безопасности используют 2 вызова доступа к документу для проверки каждой записи. В этом случае каждая операция записи использует 2 из 10 вызовов доступа, а пакетный запрос на запись использует 6 из 20 вызовов доступа.

Превышение любого из этих пределов приведет к ошибке "Доступ запрещен".

Некоторые запросы на доступ к документам могут кэшироваться, и кэшированные запросы не учитываются в лимитах.

Максимальная глубина вложенных match соответствия 10
Максимальная допустимая длина пути в сегментах внутри набора вложенных операторов match . 100
Максимальное количество переменных для захвата пути, разрешенных в наборе вложенных операторов match . 20
Максимальная глубина вызовов функций 20
Максимальное количество аргументов функции 7
Максимальное количество привязок переменных let на функцию 10
Максимальное количество рекурсивных или циклических вызовов функций 0 (не разрешено)
Максимальное количество выражений, оцениваемых за один запрос. 1000
Максимальный размер набора правил Наборы правил должны соответствовать двум ограничениям по размеру:
  • Ограничение в 256 КБ на размер исходного текста набора правил, публикуемого из консоли Firebase или из командной строки с помощью firebase deploy .
  • Ограничение в 250 КБ на размер скомпилированного набора правил, который возникает, когда Firebase обрабатывает исходный код и активирует его на бэкэнде.

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

Матчи Wildcard

Для 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 оцениваются как истинное, тогда как файл "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 map<string, string> Когда пользователь авторизован, предоставляются uid — уникальный идентификатор пользователя, и token — карта утверждений JWT Firebase Authentication . В противном случае значение будет null .
params map<string, string> Карта, содержащая параметры запроса.
path путь path , представляющий собой адрес, по которому выполняется запрос.
resource map<string, string> Новое значение ресурса, присутствующее только в запросах 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 map<string, string> Пары ключ/значение дополнительных, заданных разработчиком пользовательских метаданных.

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
Примечание: Этот продукт 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
})
Java
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!
})
Java
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 }
      }
    }
  }