Les règles de sécurité de la Firebase Realtime Database vous permettent de contrôler l'accès aux données stockées dans votre base de données. La syntaxe flexible des règles vous permet de créer des règles qui correspondent à n'importe quoi, de toutes les écritures dans votre base de données aux opérations sur des nœuds individuels.
Les règles de sécurité de la Realtime Database sont 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 plusieurs avantages : les clients ne sont pas responsables de l'application de la sécurité, les implémentations défectueuses ne compromettent pas vos données et, surtout, il n'est pas nécessaire d'avoir un arbitre intermédiaire, tel qu'un serveur, pour protéger les données du monde entier.
Cet article décrit la syntaxe et la structure de base des règles de sécurité de la 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é de la Realtime Database sont composé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 impliquées (par exemple, lecture, écriture) et les conditions dans lesquelles l'accès est autorisé ou refusé.
Dans les exemples suivants, nos conditions seront de simples instructions true et false, mais dans l'article suivant, nous aborderons des moyens plus dynamiques d'exprimer des conditions.
Ainsi, par exemple, si nous essayons 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>,
}
}
}
}Appliquons ce modèle. Supposons, par exemple, que vous suivez 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 utiles pour cette structure de données. Cet exemple illustre 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 sur 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 des 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ésumé de leurs objectifs :
| Types de règle | |
|---|---|
| .read | Décrit si et quand les utilisateurs sont autorisés à lire les données. |
| .write | Décrit si et quand les données sont autorisées à être écrites. |
| .validate | Définit l'apparence d'une valeur correctement formatée, si elle comporte des attributs enfants et le type de données. |
Variables de capture de caractères 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 de 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 Security Rules conditions de règles de sécurité plus complexes, ce que nous aborderons plus en détail
dans l'article suivant.
{ "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 chemins 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 entraînant la création d'enfants supplémentaires échouera.
{
"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 moins profondes 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 l'accès à tous les nœuds enfants situés en dessous. Prenons l'exemple de 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 enfant baz 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 enfant.
Bien que cela ne semble pas immédiatement intuitif, il s'agit d'une partie puissante du langage de règles qui permet d'implémenter des droits d'accès très complexes avec un minimum d'efforts. Nous l'illustrerons lorsque nous aborderons la sécurité basée sur l'utilisateur plus loin dans ce guide.
Notez que les règles .validate ne sont pas en cascade. Toutes les règles de validation doivent être satisfaites à 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'existe pas de règle à cet emplacement ou à un emplacement parent qui accorde l'accès. Même si tous les chemins enfants concernés sont accessibles, la lecture à l'emplacement parent échouera complètement. Prenons l'exemple de la structure suivante :
{
"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 la récupération du chemin /records/ renvoie rec1, mais pas rec2. Le résultat réel est toutefois 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 sur /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 notre Firebase console, 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
/records/ chemin. Toutefois, notez que la règle pour rec1 n'a jamais été évaluée, car elle ne figurait pas dans le chemin que nous avons demandé. Pour récupérer 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 que plusieurs règles s'appliquent à un nœud. Dans le cas où plusieurs expressions de règles identifient un nœud, la méthode d'accès est refusée si l'une des conditions est 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é de la Firebase Realtime Database :
Découvrez le concept majeur suivant du Security Rules langage, les conditions dynamiques, qui permettent à vos Security 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 courants de la sécurité et les définitions des règles de sécurité Firebase qui les traitent.