Эта страница была переведа с помощью Cloud Translation API.
Switch to English

Безопасность ваших данных

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

Структурирование Правила

Realtime Правила базы данных составлены из JavaScript-подобные выражения, содержащиеся в документе JSON. Структура ваших правил должна соответствовать структуре данных, хранящихся в базе данных. Например, предположим, что вы отслеживаете из списка сообщений и есть данные, что выглядит следующим образом:

{
  "messages": {
    "message0": {
      "content": "Hello",
      "timestamp": 1405704370369
    },
    "message1": {
      "content": "Goodbye",
      "timestamp": 1405704395231
    },
    ...
  }
}

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

{
  "rules": {
    "messages": {
      "$message": {
        // only messages from the last ten minutes can be read
        ".read": "data.child('timestamp').val() > (now - 600000)",

        // new messages must have a string content and a number timestamp
        ".validate": "newData.hasChildren(['content', 'timestamp']) && newData.child('content').isString() && newData.child('timestamp').isNumber()"
      }
    }
  }
}

Типы правил безопасности

Есть три типа правил для соблюдения безопасности: .write , .read и .validate . Вот краткий обзор их целей:

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

Предопределенные переменные

Есть целый ряд полезных, предопределенные переменные, которые могут быть доступны в определении правил безопасности. Мы будем использовать большинство из них в приведенных ниже примерах. Вот краткое описание каждого и ссылка на соответствующий API ссылку.

Предопределенные переменные
сейчас Текущее время в миллисекундах, начиная с Linux эпохи. Это особенно хорошо работает для проверки временных меток, созданных с помощью firebase.database.ServerValue.TIMESTAMP в SDK в.
корень RuleDataSnapshot , представляющий путь корня в базе данных Firebase , как она существует до попытки операции.
NewData RuleDataSnapshot , представляющий данные , как она будет существовать после попытки операции. Она включает в себя новые данные записываются и имеющиеся данные.
данные RuleDataSnapshot , представляющий данные , как она существовала до попытки операции.
$ переменных Подстановочный путь, используемый для представления идентификаторов и динамические ключей ребенка.
авт Представляет маркер полезной нагрузки прошедшего аутентификацию пользователя.

Эти переменные могут быть использованы в любом месте в правилах. Например, правила безопасности ниже гарантировать , что данные , записанные в /foo/ узел должен быть строкой менее 100 символов:

{
  "rules": {
    "foo": {
      // /foo is readable by the world
      ".read": true,

      // /foo is writable by the world
      ".write": true,

      // data written to /foo must be a string less than 100 characters
      ".validate": "newData.isString() && newData.val().length < 100"
    }
  }
}

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

Предопределенные 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/ узел true , родительский узел не имеет readOnly установлен флаг, и есть ребенок с именем foo в недавно записанных данных:

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

Чтение и запись Правила Cascade

.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 правила не каскадом. Все правила 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!

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

Хотя вы не можете использовать правила, как фильтры, вы можете ограничить доступ к подмножествам данных с помощью параметров запроса в правилах. Использование 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
строка
число
логический
ноль
Извлекает границы исполняющего запроса или возвращает нуль, если нет связанного набора.
query.limitToFirst
query.limitToLast
число
ноль
Получает ограничение на исполняющий запрос, или возвращает нулевое значение, если нет предела.

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

Обеспечение структуры данных и проверка формата и содержания данных должны быть выполнен с использованием .validate правил, которые выполняются только после того, как .write правила удается предоставить доступ. Ниже приведен пример .validate определение правила , которое позволяет только даты в формате YYYY-MM-DD между годами 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 правил являются единственным типом правила безопасности , которые не каскада. Если какое-либо правило проверки не будет работать на любой детской записи, вся операция записи будет отклонена. Кроме того, определения Validate игнорируются при удалении данных (то есть, когда новое значение записывается в null ).

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

{
  "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);
Objective-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];
стриж
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);
Objective-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];
стриж
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() правило, которое будет зависеть от того, должна ли быть разрешена делеция.

Использование $ переменных для Сегменты Path Capture

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

{
  "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 }
    }
  }
}

Анонимный чат Пример

Давайте соберем правила вместе и создать безопасный, анонимный чат приложения. Здесь мы перечислим правило, и функциональная версия чата приведена ниже:

{
  "rules": {
    // default rules are false if not specified
    // setting these to true would make ALL CHILD PATHS readable/writable
    // ".read": false,
    // ".write": false,

    "room_names": {
      // the room names can be enumerated and read
      // they cannot be modified since no write rule
      // explicitly allows this
      ".read": true,

      "$room_id": {
        // this is just for documenting the structure of rooms, since
        // they are read-only and no write rule allows this to be set
        ".validate": "newData.isString()"
      }
    },

    "messages": {
      "$room_id": {
        // the list of messages in a room can be enumerated and each
        // message could also be read individually, the list of messages
        // for a room cannot be written to in bulk
        ".read": true,

        // room we want to write a message to must be valid
        ".validate": "root.child('room_names/'+$room_id).exists()",

        "$message_id": {
          // a new message can be created if it does not exist, but it
          // cannot be modified or deleted
          ".write": "!data.exists() && newData.exists()",
          // the room attribute must be a valid key in room_names/ (the room must exist)
          // the object to write must have a name, message, and timestamp
          ".validate": "newData.hasChildren(['name', 'message', 'timestamp'])",

          // the name must be a string, longer than 0 chars, and less than 20 and cannot contain "admin"
          "name": { ".validate": "newData.isString() && newData.val().length > 0 && newData.val().length < 20 && !newData.val().contains('admin')" },

          // the message must be longer than 0 chars and less than 50
          "message": { ".validate": "newData.isString() && newData.val().length > 0 && newData.val().length < 50" },

          // messages cannot be added in the past or the future
          // clients should use firebase.database.ServerValue.TIMESTAMP
          // to ensure accurate timestamps
          "timestamp": { ".validate": "newData.val() <= now" },

          // no other fields can be included in a message
          "$other": { ".validate": false }
        }
      }
    }
  }
}

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