Правила безопасности Firebase Realtime Database позволяют контролировать доступ к данным, хранящимся в вашей базе данных. Гибкий синтаксис правил позволяет создавать правила, соответствующие чему угодно, от всех операций записи в базу данных до операций на отдельных узлах.
Правила безопасности базы данных в реальном времени представляют собой декларативную конфигурацию вашей базы данных. Это означает, что правила определяются отдельно от логики продукта. Это имеет ряд преимуществ: клиенты не несут ответственности за обеспечение безопасности, ошибки в реализации не поставят под угрозу ваши данные, и, что, пожалуй, наиболее важно, нет необходимости в промежуточном посреднике, таком как сервер, для защиты данных от внешнего мира.
В этой теме описывается базовый синтаксис и структура правил безопасности баз данных реального времени, используемых для создания полных наборов правил.
Структурирование правил безопасности
Правила безопасности баз данных в реальном времени состоят из выражений, подобных JavaScript, содержащихся в JSON-документе. Структура ваших правил должна соответствовать структуре данных, хранящихся в вашей базе данных.
Основные правила определяют набор узлов, подлежащих защите, используемые методы доступа (например, чтение, запись) и условия, при которых доступ разрешается или запрещается. В следующих примерах наши условия будут представлять собой простые утверждения типа true и false , но в следующей теме мы рассмотрим более динамичные способы выражения условий.
Например, если мы пытаемся защитить child_node под parent_node , то общий синтаксис будет следующим:
{
"rules": {
"parent_node": {
"child_node": {
".read": <condition>,
".write": <condition>,
".validate": <condition>,
}
}
}
}Давайте применим этот шаблон. Например, предположим, вы отслеживаете список сообщений и имеете данные, которые выглядят следующим образом:
{
"messages": {
"message0": {
"content": "Hello",
"timestamp": 1405704370369
},
"message1": {
"content": "Goodbye",
"timestamp": 1405704395231
},
...
}
}Ваши правила должны быть структурированы аналогичным образом. Вот набор правил для обеспечения безопасности только для чтения, который может подойти для этой структуры данных. Этот пример иллюстрирует, как мы указываем узлы базы данных, к которым применяются правила, и условия для оценки правил на этих узлах.
{ "rules": { // For requests to access the 'messages' node... "messages": { // ...and the individual wildcarded 'message' nodes beneath // (we'll cover wildcarding variables more a bit later).... "$message": { // For each message, allow a read operation if <condition>. In this // case, we specify our condition as "true", so read access is always granted. ".read": "true", // For read-only behavior, we specify that for write operations, our // condition is false. ".write": "false" } } } }
Основные правила и операции
Существует три типа правил для обеспечения безопасности в зависимости от типа операции, выполняемой над данными: .write , .read и .validate . Вот краткое описание их назначения:
| Типы правил | |
|---|---|
| .читать | Указывает, разрешено ли пользователям читать данные и когда это разрешено. |
| .писать | Указывает, разрешено ли и когда разрешается запись данных. |
| .validate | Определяет, как будет выглядеть правильно отформатированное значение, имеет ли оно дочерние атрибуты и тип данных. |
Переменные захвата с подстановочными знаками
Все операторы правил указывают на узлы. Оператор может указывать на конкретный узел или использовать переменные-захватчики с символом подстановки $ для указания на наборы узлов на определенном уровне иерархии. Используйте эти переменные-захватчики для хранения значений ключей узлов, которые будут использоваться в последующих операторах правил. Этот метод позволяет писать более сложные условия Rules , что мы подробнее рассмотрим в следующей теме.
{ "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')" } } } } }
Динамические переменные $ также можно использовать параллельно с постоянными именами путей. В этом примере мы используем переменную $other для объявления правила .validate , которое гарантирует, что widget нет дочерних элементов, кроме title и color . Любая запись, которая приведет к созданию дополнительных дочерних элементов, завершится ошибкой.
{
"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 }
}
}
}Каскад правил чтения и записи
Правила .read и .write работают сверху вниз, при этом правила с более низким уровнем доступа переопределяют правила с более высоким уровнем доступа. Если правило предоставляет права на чтение или запись по определенному пути, то оно также предоставляет доступ ко всем дочерним узлам под ним. Рассмотрим следующую структуру:
{
"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 })
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 , мы увидим, что операция чтения была отклонена, поскольку ни одно правило чтения не разрешало доступ к пути /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! })
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!
Перекрывающиеся утверждения
К узлу может применяться более одного правила. В случае, если несколько правил идентифицируют один и тот же узел, доступ к нему будет запрещен, если хотя бы одно из условий окажется false :
{
"rules": {
"messages": {
// A rule expression that applies to all nodes in the 'messages' node
"$message": {
".read": "true",
".write": "true"
},
// A second rule expression applying specifically to the 'message1` node
"message1": {
".read": "false",
".write": "false"
}
}
}
}В приведенном выше примере чтение данных из узла message1 будет запрещено, поскольку второе правило всегда false , даже если первое правило всегда true .
Следующие шаги
Вы можете углубить свои знания о правилах безопасности базы данных Firebase Realtime Database:
Изучите следующую важную концепцию языка Rules — динамические условия , которые позволяют вашим Rules проверять авторизацию пользователей, сравнивать существующие и входящие данные, проверять достоверность входящих данных, проверять структуру запросов, поступающих от клиента, и многое другое.
Ознакомьтесь с типичными сценариями использования безопасности и определениями правил безопасности Firebase, которые их охватывают .