Catch up on everthing we announced at this year's Firebase Summit. Learn more

Utiliser les conditions dans les règles de base de données en temps réel

Ce guide se fonde sur l' apprendre la langue principale Règles de sécurité Firebase guide pour montrer comment ajouter des conditions à votre base de données en temps réel Firebase Règles de sécurité.

Le bloc principal de la base de données en temps réel les règles de sécurité est la condition. Une condition est une expression booléenne qui détermine si une opération particulière doit être autorisée ou refusée. Pour connaître les règles de base, en utilisant de true et false littéraux comme conditions fonctionne prefectly bien. Mais le langage Realtime Database Security Rules vous permet d'écrire des conditions plus complexes qui peuvent :

  • Vérifier l'authentification de l'utilisateur
  • Évaluer les données existantes par rapport aux données nouvellement soumises
  • Accédez et comparez différentes parties de votre base de données
  • Valider les données entrantes
  • Utiliser la structure des requêtes entrantes pour la logique de sécurité

Utilisation de variables $ pour capturer des segments de chemin

Vous pouvez capturer des portions du chemin pour une lecture ou d' écriture en déclarant des variables de capture avec le $ préfixe. Cela sert de caractère générique et stocke la valeur de cette clé pour une utilisation dans les conditions des règles :

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

La dynamique $ variables peuvent également être utilisés en parallèle avec des noms de chemin constant. Dans cet exemple, nous utilisons l' $other variable pour déclarer une .validate règle qui garantit que widget de n'a pas d' enfant autre que le title et la 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 }
    }
  }
}

Authentification

L'un des modèles de règles de sécurité les plus courants consiste à contrôler l'accès en fonction de l'état d'authentification de l'utilisateur. Par exemple, votre application peut souhaiter autoriser uniquement les utilisateurs connectés à écrire des données.

Si votre application utilise l' authentification Firebase, la request.auth variable contient les informations d'authentification pour le client demandant des données. Pour plus d' informations sur request.auth , consultez la documentation de référence .

L'authentification Firebase s'intègre à la base de données en temps réel Firebase pour vous permettre de contrôler l'accès aux données par utilisateur à l'aide de conditions. Une fois qu'un utilisateur authentifie, la auth variable dans votre base de données en temps réel Règles de sécurité règles sera rempli avec les informations de l'utilisateur. Ces informations comprennent leur identifiant unique ( uid ) ainsi que des données de compte liées, comme un identifiant Facebook ou une adresse e - mail et d' autres informations. Si vous implémentez un fournisseur d'authentification personnalisé, vous pouvez ajouter vos propres champs à la charge utile d'authentification de votre utilisateur.

Cette section explique comment combiner le langage Firebase Realtime Database Security Rules avec les informations d'authentification de vos utilisateurs. En combinant ces deux concepts, vous pouvez contrôler l'accès aux données en fonction de l'identité de l'utilisateur.

Le auth variable

La prédéfini auth variable dans les règles est nul avant l' authentification a lieu.

Une fois qu'un utilisateur est authentifié avec Firebase l' authentification , il contiendra les attributs suivants:

fournisseur La méthode d'authentification utilisée ("mot de passe", "anonyme", "facebook", "github", "google" ou "twitter").
uide Un identifiant d'utilisateur unique, garanti unique pour tous les fournisseurs.
jeton Le contenu du jeton Firebase Auth ID. Consultez la documentation de référence pour auth.token pour plus de détails.

Voici une règle d'exemple qui utilise la auth variable pour faire en sorte que chaque utilisateur ne peut écrire sur un chemin spécifique à l' utilisateur:

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

Structurer votre base de données pour prendre en charge les conditions d'authentification

Il est généralement utile de structurer votre base de données de manière à faciliter l'écriture des règles. Un modèle commun pour le stockage des données utilisateur dans la base de données en temps réel est de stocker tous vos utilisateurs en un seul users nœud dont les enfants sont les uid valeurs pour chaque utilisateur. Si vous vouliez restreindre l'accès à ces données de sorte que seul l'utilisateur connecté puisse voir ses propres données, vos règles ressembleraient à ceci.

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

Utilisation des revendications personnalisées d'authentification

Pour les applications nécessitant un contrôle d'accès personnalisé pour différents utilisateurs, l' authentification Firebase permet aux développeurs de demandes appliqués à un utilisateur Firebase . Ces affirmations sont accesible dans la auth.token variable dans vos règles. Voici un exemple de règles qui utilisent la hasEmergencyTowel revendication personnalisée:

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

Les développeurs qui créent leurs propres jetons d'authentification personnalisés peuvent aussi ajouter à ces demandes de jetons. Ces affirmations sont disponnible sur la auth.token variable dans vos règles.

Données existantes vs nouvelles données

La prédéfini des data variables est utilisée pour faire référence aux données avant une opération d'écriture a lieu. A l' inverse, la newData variable contient les nouvelles données qui existeront si l'opération d'écriture est réussie. newData représente le résultat de la fusion des nouvelles données en cours d' écriture et des données existantes.

Pour illustrer, cette règle nous permettrait de créer de nouveaux enregistrements ou de supprimer des enregistrements existants, mais pas d'apporter des modifications aux données existantes non nulles :

// 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()"

Référencement des données dans d'autres chemins

Toutes les données peuvent être utilisées comme critère pour les règles. En utilisant les variables prédéfinies root , data et newData , nous pouvons accéder à tout chemin qu'il existerait avant ou après un événement d'écriture.

Considérons cet exemple, ce qui permet des opérations d'écriture aussi longtemps que la valeur du /allow_writes/ nœud est true , le nœud parent ne dispose pas d' un readOnly jeu de drapeau, et il y a un enfant nommé foo dans les nouvelles données écrites:

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

Validation des données

Structures de données Faire respecter et de valider le format et le contenu des données doivent être effectuées en utilisant .validate règles, qui sont gérées seulement après une .write règle réussit à accorder l' accès. Voici un exemple .validate définition d' une règle qui ne permet que les dates au format AAAA-MM-JJ entre les années 1900-2099, qui est vérifiée à l' aide d' une expression régulière.

".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])$/)"

Les .validate règles sont le seul type de règle de sécurité qui ne le font pas en cascade. Si une règle de validation échoue sur un enregistrement enfant, l'intégralité de l'opération d'écriture sera rejetée. En outre, les définitions de valider les données sont ignorées lorsque sont supprimées (qui est, lorsque la nouvelle valeur en cours d' écriture est null ).

Ceux-ci peuvent sembler être des points triviaux, mais sont en fait des fonctionnalités importantes pour écrire de puissantes règles de sécurité de base de données en temps réel Firebase. Considérez les règles suivantes :

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

Avec cette variante à l'esprit, regardez les résultats des opérations d'écriture suivantes :

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);
Objectif c
Note: Ce produit Firebase n'est pas disponible sur la cible Clip App.
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];
Rapide
Note: Ce produit Firebase n'est pas disponible sur la cible Clip App.
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);
Java
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);
DU REPOS
# 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

Maintenant , regardons la même structure, mais en utilisant .write règles au lieu de .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()"
      }
    }
  }
}

Dans cette variante, l'une des opérations suivantes réussirait :

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);
Objectif c
Note: Ce produit Firebase n'est pas disponible sur la cible Clip App.
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];
Rapide
Note: Ce produit Firebase n'est pas disponible sur la cible Clip App.
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)
Java
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);
DU REPOS
# 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

Cela illustre les différences entre .write et .validate règles. Comme l'a démontré, toutes ces règles doivent être rédigés dans .validate , à l'exception possible de la newData.hasChildren() règle, qui dépendrait de suppressions devraient être autorisées.

Règles basées sur les requêtes

Bien que vous ne pouvez pas utiliser des règles en tant que filtres , vous pouvez limiter l' accès à des sous - ensembles de données en utilisant des paramètres de requête dans vos règles. Utilisez la query. expressions dans vos règles pour accorder un accès en lecture ou en écriture en fonction des paramètres de requête.

Par exemple, la règle à base de requête suivante utilise des règles de sécurité basées sur l'utilisateur et les règles basées sur des requêtes pour restreindre l' accès aux données dans les baskets collection aux seuls paniers est propriétaire l'utilisateur actif:

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

La requête suivante, qui inclut les paramètres de requête dans la règle, aboutirait :

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

Cependant, les requêtes qui ne comprennent pas les paramètres de la règle échoueraient avec une PermissionDenied erreur:

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

Vous pouvez également utiliser des règles basées sur des requêtes pour limiter la quantité de données qu'un client télécharge via des opérations de lecture.

Par exemple, la règle suivante limite l'accès en lecture aux 1 000 premiers résultats d'une requête, par ordre de priorité :

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)

Ce qui suit query. Les expressions sont disponibles dans les règles de sécurité de la base de données en temps réel.

Expressions de règles basées sur des requêtes
Expression Taper La description
query.orderByKey
query.orderByPriority
query.orderByValue
booléen True pour les requêtes classées par clé, priorité ou valeur. Faux sinon.
requête.orderByChild chaîne de caractères
nul
Utilisez une chaîne pour représenter le chemin relatif vers un nœud enfant. Par exemple, query.orderByChild == "address/zip" . Si la requête n'est pas triée par un nœud enfant, cette valeur est nulle.
requête.startAt
requête.endAt
query.equalTo
chaîne de caractères
numéro
booléen
nul
Récupère les limites de la requête en cours d'exécution ou renvoie null s'il n'y a pas de jeu de limites.
query.limitToFirst
requête.limitToLast
numéro
nul
Récupère la limite de la requête en cours d'exécution ou renvoie null si aucune limite n'est définie.

Prochaines étapes

Après cette discussion sur les conditions, vous avez une compréhension plus sophistiquée des règles et êtes prêt à :

Apprenez à gérer les principaux cas d'utilisation et découvrez le flux de travail pour développer, tester et déployer des règles :

Fonctionnalités d'apprentissage des règles spécifiques à la base de données en temps réel :