Les règles de sécurité de Firebase Realtime Database vous permettent de contrôler l'accès aux données stockées dans votre base de données. La syntaxe des règles flexibles vous permet de créer des règles qui correspondent à toutes sortes d'éléments : toutes les écritures de votre base de données, ou bien les opérations sur des nœuds individuels.
Les règles de sécurité Realtime Database constituent une configuration déclarative pour votre base de données. Cela signifie que les règles sont définies séparément de la logique du produit. Cela présente un certain nombre d'avantages: les clients ne sont pas responsables de l'application de la sécurité, les implémentations buguées ne compromettent pas vos données et, peut-être le plus important, il n'est pas nécessaire d'avoir un arbitre intermédiaire, tel qu'un serveur, pour protéger les données du monde extérieur.
Cet article décrit la syntaxe et la structure de base des règles de sécurité de Realtime Database utilisées pour créer des ensembles de règles complets.
Structurer vos règles de sécurité
Les règles de sécurité Realtime Database sont constituées d'expressions de type JavaScript contenues dans un document JSON. La structure de vos règles doit suivre celle des données que vous avez stockées dans votre base de données.
Les règles de base identifient un ensemble de nœuds à sécuriser, les méthodes d'accès (par exemple, lecture, écriture) impliquées et les conditions sous lesquelles l'accès est autorisé ou refusé.
Dans les exemples suivants, nos conditions seront des instructions true
et false
simples, mais dans le prochain sujet, nous verrons des méthodes plus dynamiques pour exprimer des conditions.
Ainsi, si vous essayez de sécuriser un child_node
sous un parent_node
, la syntaxe générale à suivre est la suivante:
{ "rules": { "parent_node": { "child_node": { ".read": <condition>, ".write": <condition>, ".validate": <condition>, } } } }
Appliquez ce modèle. Par exemple, supposons que vous teniez une liste de messages et que vos données se présentent comme suit:
{ "messages": { "message0": { "content": "Hello", "timestamp": 1405704370369 }, "message1": { "content": "Goodbye", "timestamp": 1405704395231 }, ... } }
Vos règles doivent être structurées de manière similaire. Voici un ensemble de règles de sécurité en lecture seule qui peuvent être adaptées à cette structure de données. Cet exemple montre comment nous spécifions les nœuds de base de données auxquels les règles s'appliquent et les conditions d'évaluation des règles au niveau de ces nœuds.
{ "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" } } } }
Opérations de base sur les règles
Il existe trois types de règles pour appliquer la sécurité en fonction du type d'opération effectuée sur les données: .write
, .read
et .validate
. Voici un bref récapitulatif de leurs objectifs:
Types de règles | |
---|---|
.read | Indique si les utilisateurs sont autorisés à lire les données et quand. |
.write | Décrit si et quand l'écriture de données est autorisée. |
.validate | Définit à quoi ressemblera une valeur correctement formatée, si elle possède des attributs enfants et le type de données. |
Variables de capture génériques
Toutes les instructions de règles pointent vers des nœuds. Une instruction peut pointer vers un nœud spécifique ou utiliser des variables de capture avec des caractères génériques $
pour pointer vers des ensembles de nœuds à un niveau de la hiérarchie. Utilisez ces variables de capture pour stocker la valeur des clés de nœud à utiliser dans les instructions de règles suivantes. Cette technique vous permet d'écrire des conditions Rules plus complexes, que nous aborderons plus en détail dans le prochain sujet.
{ "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')" } } } } }
Les variables dynamiques $
peuvent également être utilisées en parallèle avec des noms de chemin d'accès constants. Dans cet exemple, nous utilisons la variable $other
pour déclarer une règle .validate
qui garantit que widget
n'a pas d'enfants autres que title
et color
.
Toute écriture qui entraînerait la création d'enfants supplémentaires échouerait.
{ "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 } } } }
Cascade des règles de lecture et d'écriture
Les règles .read
et .write
fonctionnent de haut en bas, les règles plus superficielles remplaçant les règles plus profondes. Si une règle accorde des autorisations de lecture ou d'écriture sur un chemin particulier, elle accorde également un accès à tous les nœuds enfants de ce chemin. Prenons la structure suivante:
{ "rules": { "foo": { // allows read to /foo/* ".read": "data.child('baz').val() === true", "bar": { /* ignored, since read was allowed already */ ".read": false } } } }
Cette structure de sécurité permet de lire /bar/
chaque fois que /foo/
contient un baz
enfant avec la valeur true
.
La règle ".read": false
sous /foo/bar/
n'a aucun effet ici, car l'accès ne peut pas être révoqué par un chemin d'accès enfant.
Bien que cela ne semble pas immédiatement intuitif, il constitue une partie importante du langage des règles et permet d'implémenter des privilèges d'accès très complexes avec un minimum d'efforts. Nous verrons cela plus loin dans ce guide lorsque nous aborderons la sécurité basée sur l'utilisateur.
Notez que les règles .validate
ne sont pas en cascade. Toutes les règles de validation doivent être respectées à tous les niveaux de la hiérarchie pour qu'une écriture soit autorisée.
Les règles ne sont pas des filtres
Les règles sont appliquées de manière atomique. Cela signifie qu'une opération de lecture ou d'écriture échoue immédiatement s'il n'y a pas de règle à cet emplacement ou à un emplacement parent qui accorde l'accès. Même si tous les chemins d'accès enfant concernés sont accessibles, la lecture à l'emplacement parent échoue complètement. Prenons cette structure:
{ "rules": { "records": { "rec1": { ".read": true }, "rec2": { ".read": false } } } }
Sans comprendre que les règles sont évaluées de manière atomique, il peut sembler que l'extraction du chemin /records/
renvoie rec1
, mais pas rec2
. Cependant, le résultat réel est une erreur:
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 }];
Swift
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 }); });
REST
curl https://docs-examples.firebaseio.com/rest/records/ # response returns a PERMISSION_DENIED error
Étant donné que l'opération de lecture à /records/
est atomique et qu'aucune règle de lecture n'accorde l'accès à toutes les données sous /records/
, une erreur PERMISSION_DENIED
est générée. Si nous évaluons cette règle dans le simulateur de sécurité de la console Firebase, nous pouvons voir que l'opération de lecture a été refusée, car aucune règle de lecture n'autorisait l'accès au chemin /records/
. Notez toutefois que la règle pour rec1
n'a jamais été évaluée, car elle ne figurait pas dans le chemin d'accès que nous avons demandé. Pour extraire rec1
, nous devons y accéder directement:
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! }];
Swift
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 } });
REST
curl https://docs-examples.firebaseio.com/rest/records/rec1 # SUCCESS!
Instructions qui se chevauchent
Il est possible qu'une règle s'applique à plusieurs nœuds. Dans le cas où plusieurs expressions de règles identifient un nœud, la méthode d'accès est refusée si au moins une des conditions est définie sur 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" } } } }
Dans l'exemple ci-dessus, les lectures du nœud message1
seront refusées, car la deuxième règle est toujours false
, même si la première règle est toujours true
.
Étapes suivantes
Vous pouvez approfondir vos connaissances sur les règles de sécurité Firebase Realtime Database:
Découvrez le prochain concept majeur du langage Rules, les conditions dynamiques, qui permettent à votre Rules de vérifier l'autorisation de l'utilisateur, de comparer les données existantes et entrantes, de valider les données entrantes, de vérifier la structure des requêtes provenant du client, et plus encore.
Consultez les cas d'utilisation de sécurité courants et les définitions des règles de sécurité Firebase qui les couvrent.