Google is committed to advancing racial equity for Black communities. See how.
Эта страница была переведа с помощью Cloud Translation API.
Switch to English

Условия использования в правилах базы данных реального времени

Это руководство основано на руководстве по основному языку правил безопасности Firebase, чтобы показать, как добавить условия в правила безопасности базы данных Firebase в реальном времени.

Основным строительным блоком правил безопасности баз данных в реальном времени является условие . Условие - это логическое выражение, которое определяет, следует ли разрешить или запретить конкретную операцию. Для основных правил использование true и false литералов в качестве условий отлично работает. Но язык правил безопасности баз данных в реальном времени дает вам возможность писать более сложные условия, которые могут:

  • Проверить аутентификацию пользователя
  • Сравните существующие данные с новыми данными
  • Доступ и сравнение различных частей вашей базы данных
  • Проверить входящие данные
  • Используйте структуру входящих запросов для логики безопасности

Использование $ Variables для захвата сегментов пути

Вы можете захватить части пути для чтения или записи, объявив переменные захвата с префиксом $ . Это служит подстановкой и сохраняет значение этого ключа для использования внутри условий правил:

{
  "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 нет дочерних .validate , кроме 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 }
    }
  }
}

Аутентификация

Один из наиболее распространенных шаблонов правил безопасности - это управление доступом на основе состояния аутентификации пользователя. Например, вашему приложению может потребоваться разрешить запись данных только вошедшим в систему пользователям.

Если ваше приложение использует аутентификацию Firebase, переменная request.auth содержит информацию аутентификации для клиента, запрашивающего данные. Для получения дополнительных сведений о request.auth см. Справочную документацию .

Firebase Authentication интегрируется с базой данных Firebase Realtime, чтобы вы могли контролировать доступ к данным для каждого пользователя с помощью условий. После аутентификации пользователя переменная auth в правилах правил безопасности баз данных в реальном времени будет заполнена информацией о пользователе. Эта информация включает в себя их уникальный идентификатор ( uid ), а также данные связанных учетных записей, такие как идентификатор Facebook или адрес электронной почты, и другую информацию. Если вы реализуете настраиваемый поставщик аутентификации, вы можете добавить свои собственные поля в полезные данные аутентификации пользователя.

В этом разделе объясняется, как объединить язык правил безопасности баз данных Firebase в реальном времени с аутентификационной информацией о ваших пользователях. Объединив эти две концепции, вы можете контролировать доступ к данным на основе личности пользователя.

Переменная auth

auth в правилах переменная аутентификации имеет значение null до того, как произойдет аутентификация.

После аутентификации пользователя с помощью Firebase Authentication он будет содержать следующие атрибуты:

провайдер Используемый метод аутентификации («пароль», «анонимный», «facebook», «github», «google» или «twitter»).
uid Уникальный идентификатор пользователя, гарантированно уникальный для всех провайдеров.
жетон Содержимое токена Firebase Auth ID. Дополнительные сведения см. В справочной документации для auth.token .

Вот пример правила, которое использует переменную auth чтобы гарантировать, что каждый пользователь может писать только по пути, заданному пользователем:

{
  "rules": {
    "users": {
      "$user_id": {
        // grants write access to the owner of this user account
        // whose uid must exactly match the key ($user_id)
        ".write": "$user_id === auth.uid"
      }
    }
  }
}

Структурирование вашей базы данных для поддержки условий аутентификации

Обычно полезно структурировать базу данных таким образом, чтобы облегчить написание правил. Один из распространенных способов хранения пользовательских данных в базе данных реального времени - хранить всех ваших пользователей в одном узле users чьи дочерние элементы являются значениями uid для каждого пользователя. Если вы хотите ограничить доступ к этим данным так, чтобы только зарегистрированный пользователь мог видеть свои собственные данные, ваши правила выглядели бы примерно так.

{
  "rules": {
    "users": {
      "$uid": {
        ".read": "auth != null && auth.uid == $uid"
      }
    }
  }
}

Работа с настраиваемыми утверждениями аутентификации

Для приложений, которым требуется настраиваемый контроль доступа для разных пользователей, Firebase Authentication позволяет разработчикам устанавливать заявки на пользователя Firebase . Эти утверждения доступны в переменной auth.token в ваших правилах. Вот пример правил, в которых используется настраиваемое утверждение hasEmergencyTowel :

{
  "rules": {
    "frood": {
      // A towel is about the most massively useful thing an interstellar
      // hitchhiker can have
      ".read": "auth.token.hasEmergencyTowel === true"
    }
  }
}

Разработчики, создающие свои собственные токены аутентификации, могут при желании добавлять к этим токенам утверждения. Эти утверждения доступны для переменной auth.token в ваших правилах.

Существующие данные против новых данных

Предопределенная переменная data используется для ссылки на данные до того, как будет выполнена операция записи. И наоборот, переменная newData содержит новые данные, которые будут существовать, если операция записи будет успешной. newData представляет собой объединенный результат записываемых новых данных и существующих данных.

Чтобы проиллюстрировать, это правило позволит нам создавать новые записи или удалять существующие, но не вносить изменения в существующие ненулевые данные:

// we can write as long as old data or new data does not exist
// in other words, if this is a delete or a create, but not an update
".write": "!data.exists() || !newData.exists()"

Ссылки на данные в других путях

Любые данные могут использоваться как критерий для правил. Используя предопределенные переменные root , data и newData , мы можем получить доступ к любому пути, так как он существовал бы до или после события записи.

Рассмотрим этот пример, который разрешает операции записи до тех пор, пока значение /allow_writes/ node равно true , у родительского узла не установлен флаг readOnly и есть дочерний элемент с именем foo во вновь записанных данных:

".write": "root.child('allow_writes').val() === true &&
          !data.parent().child('readOnly').exists() &&
          newData.child('foo').exists()"

Проверка данных

Обеспечение соблюдения структур данных и проверка формата и содержания данных должны выполняться с использованием правил .validate , которые запускаются только после того, как правило .write успешно предоставляет доступ. Ниже приведен пример определения правила .validate которое допускает только даты в формате ГГГГ-ММ-ДД между 1900-2099 годами и проверяется с помощью регулярного выражения.

".validate": "newData.isString() &&
              newData.val().matches(/^(19|20)[0-9][0-9][-\\/. ](0[1-9]|1[012])[-\\/. ](0[1-9]|[12][0-9]|3[01])$/)"

Правила .validate - единственный тип правил безопасности, которые не каскадируются. Если какое-либо правило проверки не выполняется для какой-либо дочерней записи, вся операция записи будет отклонена. Кроме того, определения проверки игнорируются при удалении данных (то есть, когда новое записываемое значение равно null ).

Это может показаться банальным, но на самом деле это важные функции для написания эффективных правил безопасности баз данных Firebase в реальном времени. Учитывайте следующие правила:

{
  "rules": {
    // write is allowed for all paths
    ".write": true,
    "widget": {
      // a valid widget must have attributes "color" and "size"
      // allows deleting widgets (since .validate is not applied to delete rules)
      ".validate": "newData.hasChildren(['color', 'size'])",
      "size": {
        // the value of "size" must be a number between 0 and 99
        ".validate": "newData.isNumber() &&
                      newData.val() >= 0 &&
                      newData.val() <= 99"
      },
      "color": {
        // the value of "color" must exist as a key in our mythical
        // /valid_colors/ index
        ".validate": "root.child('valid_colors/' + newData.val()).exists()"
      }
    }
  }
}

Имея в виду этот вариант, посмотрите на результаты для следующих операций записи:

JavaScript
var ref = db.ref("/widget");

// PERMISSION_DENIED: does not have children color and size
ref.set('foo');

// PERMISSION DENIED: does not have child color
ref.set({size: 22});

// PERMISSION_DENIED: size is not a number
ref.set({ size: 'foo', color: 'red' });

// SUCCESS (assuming 'blue' appears in our colors list)
ref.set({ size: 21, color: 'blue'});

// If the record already exists and has a color, this will
// succeed, otherwise it will fail since newData.hasChildren(['color', 'size'])
// will fail to validate
ref.child('size').set(99);
Цель-C
FIRDatabaseReference *ref = [[[FIRDatabase database] reference] child: @"widget"];

// PERMISSION_DENIED: does not have children color and size
[ref setValue: @"foo"];

// PERMISSION DENIED: does not have child color
[ref setValue: @{ @"size": @"foo" }];

// PERMISSION_DENIED: size is not a number
[ref setValue: @{ @"size": @"foo", @"color": @"red" }];

// SUCCESS (assuming 'blue' appears in our colors list)
[ref setValue: @{ @"size": @21, @"color": @"blue" }];

// If the record already exists and has a color, this will
// succeed, otherwise it will fail since newData.hasChildren(['color', 'size'])
// will fail to validate
[[ref child:@"size"] setValue: @99];
Swift
var ref = FIRDatabase.database().reference().child("widget")

// PERMISSION_DENIED: does not have children color and size
ref.setValue("foo")

// PERMISSION DENIED: does not have child color
ref.setValue(["size": "foo"])

// PERMISSION_DENIED: size is not a number
ref.setValue(["size": "foo", "color": "red"])

// SUCCESS (assuming 'blue' appears in our colors list)
ref.setValue(["size": 21, "color": "blue"])

// If the record already exists and has a color, this will
// succeed, otherwise it will fail since newData.hasChildren(['color', 'size'])
// will fail to validate
ref.child("size").setValue(99);
Ява
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference("widget");

// PERMISSION_DENIED: does not have children color and size
ref.setValue("foo");

// PERMISSION DENIED: does not have child color
ref.child("size").setValue(22);

// PERMISSION_DENIED: size is not a number
Map<String,Object> map = new HashMap<String, Object>();
map.put("size","foo");
map.put("color","red");
ref.setValue(map);

// SUCCESS (assuming 'blue' appears in our colors list)
map = new HashMap<String, Object>();
map.put("size", 21);
map.put("color","blue");
ref.setValue(map);

// If the record already exists and has a color, this will
// succeed, otherwise it will fail since newData.hasChildren(['color', 'size'])
// will fail to validate
ref.child("size").setValue(99);
ОСТАЛЬНОЕ
# PERMISSION_DENIED: does not have children color and size
curl -X PUT -d 'foo' \
https://docs-examples.firebaseio.com/rest/securing-data/example.json

# PERMISSION DENIED: does not have child color
curl -X PUT -d '{"size": 22}' \
https://docs-examples.firebaseio.com/rest/securing-data/example.json

# PERMISSION_DENIED: size is not a number
curl -X PUT -d '{"size": "foo", "color": "red"}' \
https://docs-examples.firebaseio.com/rest/securing-data/example.json

# SUCCESS (assuming 'blue' appears in our colors list)
curl -X PUT -d '{"size": 21, "color": "blue"}' \
https://docs-examples.firebaseio.com/rest/securing-data/example.json

# If the record already exists and has a color, this will
# succeed, otherwise it will fail since newData.hasChildren(['color', 'size'])
# will fail to validate
curl -X PUT -d '99' \
https://docs-examples.firebaseio.com/rest/securing-data/example/size.json

Теперь давайте посмотрим на ту же структуру, но с использованием правил .write вместо .validate :

{
  "rules": {
    // this variant will NOT allow deleting records (since .write would be disallowed)
    "widget": {
      // a widget must have 'color' and 'size' in order to be written to this path
      ".write": "newData.hasChildren(['color', 'size'])",
      "size": {
        // the value of "size" must be a number between 0 and 99, ONLY IF WE WRITE DIRECTLY TO SIZE
        ".write": "newData.isNumber() && newData.val() >= 0 && newData.val() <= 99"
      },
      "color": {
        // the value of "color" must exist as a key in our mythical valid_colors/ index
        // BUT ONLY IF WE WRITE DIRECTLY TO COLOR
        ".write": "root.child('valid_colors/'+newData.val()).exists()"
      }
    }
  }
}

В этом варианте любая из следующих операций завершится успешно:

JavaScript
var ref = new Firebase(URL + "/widget");

// ALLOWED? Even though size is invalid, widget has children color and size,
// so write is allowed and the .write rule under color is ignored
ref.set({size: 99999, color: 'red'});

// ALLOWED? Works even if widget does not exist, allowing us to create a widget
// which is invalid and does not have a valid color.
// (allowed by the write rule under "color")
ref.child('size').set(99);
Цель-C
Firebase *ref = [[Firebase alloc] initWithUrl:URL];

// ALLOWED? Even though size is invalid, widget has children color and size,
// so write is allowed and the .write rule under color is ignored
[ref setValue: @{ @"size": @9999, @"color": @"red" }];

// ALLOWED? Works even if widget does not exist, allowing us to create a widget
// which is invalid and does not have a valid color.
// (allowed by the write rule under "color")
[[ref childByAppendingPath:@"size"] setValue: @99];
Swift
var ref = Firebase(url:URL)

// ALLOWED? Even though size is invalid, widget has children color and size,
// so write is allowed and the .write rule under color is ignored
ref.setValue(["size": 9999, "color": "red"])

// ALLOWED? Works even if widget does not exist, allowing us to create a widget
// which is invalid and does not have a valid color.
// (allowed by the write rule under "color")
ref.childByAppendingPath("size").setValue(99)
Ява
Firebase ref = new Firebase(URL + "/widget");

// ALLOWED? Even though size is invalid, widget has children color and size,
// so write is allowed and the .write rule under color is ignored
Map<String,Object> map = new HashMap<String, Object>();
map.put("size", 99999);
map.put("color", "red");
ref.setValue(map);

// ALLOWED? Works even if widget does not exist, allowing us to create a widget
// which is invalid and does not have a valid color.
// (allowed by the write rule under "color")
ref.child("size").setValue(99);
ОСТАЛЬНОЕ
# ALLOWED? Even though size is invalid, widget has children color and size,
# so write is allowed and the .write rule under color is ignored
curl -X PUT -d '{size: 99999, color: "red"}' \
https://docs-examples.firebaseio.com/rest/securing-data/example.json

# ALLOWED? Works even if widget does not exist, allowing us to create a widget
# which is invalid and does not have a valid color.
# (allowed by the write rule under "color")
curl -X PUT -d '99' \
https://docs-examples.firebaseio.com/rest/securing-data/example/size.json

Это иллюстрирует различия между .write и .validate . Как было показано, все эти правила должны быть написаны с использованием .validate , за возможным исключением newData.hasChildren() , которое будет зависеть от того, следует ли разрешить удаление.

Правила на основе запросов

Хотя вы не можете использовать правила в качестве фильтров , вы можете ограничить доступ к подмножествам данных, используя параметры запроса в ваших правилах. Используйте query. выражения в ваших правилах, чтобы предоставить доступ для чтения или записи на основе параметров запроса.

Например, следующее правило на основе запроса использует правила безопасности на основе пользователей и правила на основе запросов для ограничения доступа к данным в коллекции baskets только baskets для покупок, которыми владеет активный пользователь:

"baskets": {
  ".read": "auth.uid != null &&
            query.orderByChild == 'owner' &&
            query.equalTo == auth.uid" // restrict basket access to owner of basket
}

Следующий запрос, который включает параметры запроса в правиле, будет успешным:

db.ref("baskets").orderByChild("owner")
                 .equalTo(auth.currentUser.uid)
                 .on("value", cb)                 // Would succeed

Однако запросы, которые не включают параметры в правиле, завершатся ошибкой PermissionDenied :

db.ref("baskets").on("value", cb)                 // Would fail with PermissionDenied

Вы также можете использовать правила на основе запросов, чтобы ограничить объем данных, загружаемых клиентом посредством операций чтения.

Например, следующее правило ограничивает доступ для чтения только первыми 1000 результатами запроса в порядке приоритета:

messages: {
  ".read": "query.orderByKey &&
            query.limitToFirst <= 1000"
}

// Example queries:

db.ref("messages").on("value", cb)                // Would fail with PermissionDenied

db.ref("messages").limitToFirst(1000)
                  .on("value", cb)                // Would succeed (default order by key)

Следующий query. выражения доступны в Правилах безопасности баз данных в реальном времени.

Выражения правил на основе запросов
Выражение Тип Описание
query.orderByKey
query.orderByPriority
query.orderByValue
логический Верно для запросов, упорядоченных по ключу, приоритету или значению. В противном случае неверно.
query.orderByChild строка
ноль
Используйте строку для представления относительного пути к дочернему узлу. Например, query.orderByChild == "address/zip" . Если запрос не упорядочен дочерним узлом, это значение равно нулю.
query.startAt
query.endAt
query.equalTo
строка
число
логический
ноль
Извлекает границы выполняемого запроса или возвращает null, если не установлено никаких границ.
query.limitToFirst
query.limitToLast
число
ноль
Извлекает ограничение на выполняемый запрос или возвращает null, если ограничение не установлено.

Следующие шаги

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

Узнайте, как обрабатывать основные варианты использования, и изучите рабочий процесс разработки, тестирования и развертывания правил:

Изучите особенности правил, характерные для базы данных реального времени: