Ce guide s'appuie sur le guide principal des règles de sécurité Firebase pour montrer comment ajouter des conditions à vos règles de sécurité Firebase Realtime Database.
La condition est le composant principal des règles de sécurité Realtime Database. A
condition est une expression booléenne qui détermine si une opération particulière
doit être autorisée ou refusée. Pour les règles de base, utilisez des littéraux true
et false
en tant que
fonctionne très bien. Toutefois, le langage des règles de sécurité de Realtime Database vous permet d'écrire des conditions plus complexes qui peuvent :
- Vérifier l'authentification des utilisateurs
- Comparer les données existantes aux données nouvellement envoyées
- Accéder à différentes parties de votre base de données et les comparer
- Valider les données entrantes
- Utiliser la structure des requêtes entrantes pour la logique de sécurité
Utiliser des variables $ pour capturer des segments de chemin d'accès
Vous pouvez capturer des parties du chemin d'accès pour une lecture ou une écriture en déclarant
capturez les variables avec le préfixe $
.
Il s'agit d'un joker qui stocke la valeur de cette clé pour l'utiliser dans les conditions de 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')" } } } } }
Les variables $
dynamiques peuvent également être utilisées en parallèle avec des noms de chemin constant. 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 } } } }
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 autoriser uniquement connectés pour écrire des données.
Si votre application utilise Firebase Authentication, la variable request.auth
contient les informations d'authentification du client demandant des données.
Pour plus d'informations sur request.auth
, consultez la documentation de référence.
Firebase Authentication s'intègre à Firebase Realtime Database pour vous permettre de contrôler les données
l'accès par utilisateur à l'aide de conditions. Une fois qu'un utilisateur s'est authentifié, auth
dans vos règles de règles de sécurité Realtime Database sera renseignée avec le paramètre
des informations. (par exemple, son identifiant unique (uid
))
ainsi que les données de compte associées, telles qu'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 l'utilisateur.
Cette section explique comment combiner le langage des règles de sécurité de base de données Firebase Realtime Database avec des informations d'authentification concernant vos utilisateurs. En combinant ces deux concepts, vous pouvez contrôler l'accès aux données en fonction de l'identité des utilisateurs.
Variable auth
La variable auth
prédéfinie dans les règles est nulle avant
l'authentification a lieu.
Une fois qu'un utilisateur est authentifié avec Firebase Authentication, il contient les attributs suivants :
fournisseur | Méthode d'authentification utilisée ("mot de passe", "anonyme", "facebook", "github", "google", ou "twitter"). |
uid | ID utilisateur unique, qui est garanti comme étant unique pour tous les fournisseurs. |
jeton |
Contenu du jeton d'ID Firebase Auth. Pour en savoir plus, consultez la documentation de référence de auth.token .
|
Voici un exemple de règle qui utilise la variable auth
pour s'assurer que chaque utilisateur ne peut écrire que dans 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
Rules plus facilement. Un modèle courant de stockage des données utilisateur dans Realtime Database est
pour stocker tous les utilisateurs dans un seul nœud users
dont les enfants sont
les valeurs uid
pour chaque utilisateur. Si vous souhaitez limiter l'accès à ces données afin que seul l'utilisateur connecté puisse voir ses propres données, vos règles se présenteront comme suit.
{ "rules": { "users": { "$uid": { ".read": "auth !== null && auth.uid === $uid" } } } }
Utiliser des revendications personnalisées d'authentification
Pour les applications qui nécessitent un contrôle des accès personnalisé pour différents utilisateurs, Firebase Authentication permet aux développeurs de définir des revendications sur un utilisateur Firebase.
Ces revendications sont accessibles dans la variable auth.token
de vos règles.
Voici un exemple de règles qui utilisent la revendication personnalisée hasEmergencyTowel
:
{ "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 éventuellement ajouter des revendications à ces jetons. Ces
Les revendications sont disponibles au niveau de la variable auth.token
de vos règles.
Données existantes par rapport aux nouvelles données
La variable data
prédéfinie permet de faire référence aux données avant qu'une opération d'écriture ne soit effectuée. À l'inverse, la variable newData
contient les nouvelles données qui existeront si l'opération d'écriture aboutit.
newData
représente le résultat fusionné des nouvelles données écrites et des données existantes.
Ainsi, cette règle nous permettrait de créer des enregistrements ou de supprimer uns, mais pas pour modifier les données non nulles existantes:
// 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érencer des données dans d'autres chemins
N'importe quelle donnée peut être utilisée comme critère des règles. À l'aide de la classe
les variables root
, data
et newData
, nous
peut accéder à n'importe quel chemin tel qu'il existe
avant ou après un événement d'écriture.
Prenons l'exemple suivant, qui autorise les opérations d'écriture tant que la valeur du paramètre
Le nœud /allow_writes/
est true
, le nœud parent n'a pas de
readOnly
et qu'il y a un enfant nommé foo
dans
les données nouvellement écrites:
".write": "root.child('allow_writes').val() === true && !data.parent().child('readOnly').exists() && newData.child('foo').exists()"
Valider les données
L'application des structures de données et la validation du format et du contenu des données doivent être effectuées à l'aide de règles .validate
, qui ne sont exécutées qu'après qu'une règle .write
a réussi à accorder l'accès. Voici un exemple :
Définition de la règle .validate
qui n'autorise que les dates au format
AAAA-MM-JJ entre les années 1900 et 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 règles .validate
sont le seul type de règle de sécurité qui ne peut pas être introduit en cascade. Si une règle de validation échoue pour un enregistrement enfant, l'ensemble de l'opération d'écriture est rejeté.
De plus, les définitions de validation sont ignorées lorsque les données sont supprimées (c'est-à-dire lorsque la nouvelle valeur
en cours d'écriture est null
).
Ces points peuvent sembler anodins, mais ils sont en fait des fonctionnalités importantes pour écrire des règles de sécurité Firebase Realtime Database efficaces. Tenez compte des 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()" } } } }
En gardant cette variante à l'esprit, examinez 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);
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];
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);
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);
REST
# 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
Examinons maintenant la même structure, mais en utilisant des règles .write
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);
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];
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)
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);
REST
# 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 les règles .write
et .validate
.
Comme indiqué, toutes ces règles doivent être écrites à l'aide de .validate
, avec le paramètre
exception possible de la règle newData.hasChildren()
, qui dépendrait de la manière
les suppressions doivent être autorisées.
Règles basées sur des requêtes
Bien que vous ne puissiez pas utiliser de règles comme filtres, vous pouvez limiter l'accès à des sous-ensembles de données à l'aide de paramètres de requête dans vos règles.
Utilisez des expressions query.
dans vos règles pour accorder un accès en lecture ou en écriture basé sur
paramètres de requête.
Par exemple, la règle basée sur les requêtes suivante utilise des règles de sécurité basées sur l'utilisateur.
et des règles basées sur des requêtes pour restreindre l'accès aux données de la collection baskets
uniquement aux paniers appartenant à 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, aboutit :
db.ref("baskets").orderByChild("owner")
.equalTo(auth.currentUser.uid)
.on("value", cb) // Would succeed
Toutefois, les requêtes qui n'incluent pas les paramètres de la règle échouent avec une erreur PermissionDenied
:
db.ref("baskets").on("value", cb) // Would fail with PermissionDenied
Vous pouvez également utiliser des règles basées sur les 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, classés par 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)
Les expressions query.
suivantes sont disponibles dans les règles de sécurité de Realtime Database.
Expressions de règles basées sur des requêtes | ||
---|---|---|
Expression | Type | Description |
query.orderByKey query.orderByPriority query.orderByValue |
booléen | "True" pour les requêtes triées par clé, priorité ou valeur. Sinon, cette valeur est "false". |
query.orderByChild | chaîne null |
Utilisez une chaîne pour représenter le chemin relatif à un nœud enfant. Exemple : query.orderByChild === "address/zip" . Si la requête n'est pas triée par un nœud enfant, cette valeur est nulle.
|
query.startAt query.endAt query.equalTo |
chaîne numéro Booléen nul |
Récupère les limites de la requête en cours d'exécution, ou renvoie la valeur "null" si n'est associé à aucune limite. |
query.limitToFirst query.limitToLast |
numéro nul |
Récupère la limite de la requête en cours d'exécution ou renvoie la valeur nulle si aucune limite n'est définie. |
Étapes suivantes
Après cette discussion sur les conditions, vous avez une compréhension plus sophistiquée de Rules et vous êtes prêt à :
Apprenez à gérer les principaux cas d'utilisation et découvrez le workflow de développement, en testant et en déployant Rules:
- Découvrez l'ensemble complet des variables Rules prédéfinies que vous pouvez utiliser pour créer des conditions.
- Rédigez des règles correspondant à des scénarios courants.
- Approfondissez vos connaissances en examinant les situations dans lesquelles vous devez identifier et éviter les règles non sécurisées.
- Découvrez la suite d'émulateurs locaux Firebase et comment l'utiliser pour tester Rules.
- Consultez les méthodes disponibles pour déployer Rules.
Découvrez les fonctionnalités Rules spécifiques à Realtime Database:
- Découvrez comment indexer votre Realtime Database.
- Consultez l'API REST pour déployer Rules.