Esta guía se basa en la guía de lenguaje de reglas de seguridad de Firebase Core para mostrar cómo agregar condiciones a sus reglas de seguridad de base de datos en tiempo real de Firebase.
El componente principal de las reglas de seguridad de la base de datos en tiempo real es la condición . Una condición es una expresión booleana que determina si se debe permitir o denegar una operación en particular. Para las reglas básicas, el uso de literales true
y false
como condiciones funciona perfectamente bien. Pero el lenguaje de reglas de seguridad de la base de datos en tiempo real le brinda formas de escribir condiciones más complejas que pueden:
- Comprobar la autenticación de usuario
- Evaluar los datos existentes contra los datos enviados recientemente
- Acceda y compare diferentes partes de su base de datos
- Validar datos entrantes
- Utilice la estructura de las consultas entrantes para la lógica de seguridad
Uso de variables $ para capturar segmentos de ruta
Puede capturar partes de la ruta para lectura o escritura declarando variables de captura con el prefijo $
. Esto sirve como comodín y almacena el valor de esa clave para usar dentro de las condiciones de las reglas:
{ "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')" } } } } }
Las variables dinámicas $
también se pueden usar en paralelo con nombres de rutas constantes. En este ejemplo, usamos la variable $other
para declarar una regla .validate
que garantiza que el widget
no tenga elementos secundarios además de title
y color
. Cualquier escritura que resulte en la creación de elementos secundarios adicionales fallará.
{ "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 } } } }
Autenticación
Uno de los patrones de reglas de seguridad más comunes es controlar el acceso según el estado de autenticación del usuario. Por ejemplo, es posible que su aplicación desee permitir que solo los usuarios registrados escriban datos.
Si su aplicación usa Firebase Authentication, la variable request.auth
contiene la información de autenticación para el cliente que solicita los datos. Para obtener más información acerca de request.auth
, consulte la documentación de referencia .
Firebase Authentication se integra con Firebase Realtime Database para permitirle controlar el acceso a los datos por usuario mediante condiciones. Una vez que un usuario se autentica, la variable de auth
en sus reglas de seguridad de la base de datos en tiempo real se completará con la información del usuario. Esta información incluye su identificador único ( uid
), así como los datos de la cuenta vinculada, como una identificación de Facebook o una dirección de correo electrónico, y otra información. Si implementa un proveedor de autenticación personalizado, puede agregar sus propios campos a la carga útil de autenticación de su usuario.
En esta sección, se explica cómo combinar el lenguaje de las reglas de seguridad de la base de datos en tiempo real de Firebase con la información de autenticación de sus usuarios. Al combinar estos dos conceptos, puede controlar el acceso a los datos en función de la identidad del usuario.
La variable de auth
La variable de auth
predefinida en las reglas es nula antes de que tenga lugar la autenticación.
Una vez que un usuario se autentica con Firebase Authentication , contendrá los siguientes atributos:
proveedor | El método de autenticación utilizado ("contraseña", "anónimo", "facebook", "github", "google" o "twitter"). |
fluido | Una identificación de usuario única, garantizada para ser única en todos los proveedores. |
simbólico | El contenido del token de ID de autenticación de Firebase. Consulte la documentación de referencia de auth.token para obtener más detalles. |
Aquí hay una regla de ejemplo que usa la variable auth
para garantizar que cada usuario solo pueda escribir en una ruta específica del usuario:
{ "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" } } } }
Estructuración de su base de datos para soportar las condiciones de autenticación
Por lo general, es útil estructurar su base de datos de una manera que facilite la escritura de Reglas. Un patrón común para almacenar datos de usuario en Realtime Database es almacenar todos sus usuarios en un solo nodo de users
cuyos hijos son los valores uid
para cada usuario. Si quisiera restringir el acceso a estos datos de modo que solo el usuario que inició sesión pueda ver sus propios datos, sus reglas se verían así.
{ "rules": { "users": { "$uid": { ".read": "auth !== null && auth.uid === $uid" } } } }
Trabajar con notificaciones personalizadas de autenticación
Para las aplicaciones que requieren un control de acceso personalizado para diferentes usuarios, Firebase Authentication permite a los desarrolladores establecer reclamos en un usuario de Firebase . Estos reclamos son accesibles en la variable auth.token
en sus reglas. Aquí hay un ejemplo de reglas que hacen uso del reclamo personalizado hasEmergencyTowel
:
{ "rules": { "frood": { // A towel is about the most massively useful thing an interstellar // hitchhiker can have ".read": "auth.token.hasEmergencyTowel === true" } } }
Los desarrolladores que crean sus propios tokens de autenticación personalizados pueden, opcionalmente, agregar notificaciones a estos tokens. Estos reclamos están disponibles en la variable auth.token
en sus reglas.
Datos existentes frente a datos nuevos
La variable data
predefinida se utiliza para hacer referencia a los datos antes de que tenga lugar una operación de escritura. Por el contrario, la variable newData
contiene los nuevos datos que existirán si la operación de escritura es exitosa. newData
representa el resultado combinado de los nuevos datos que se escriben y los datos existentes.
Para ilustrar, esta regla nos permitiría crear nuevos registros o eliminar los existentes, pero no realizar cambios en los datos no nulos existentes:
// 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()"
Hacer referencia a datos en otras rutas
Cualquier dato se puede utilizar como criterio para las reglas. Usando las variables predefinidas root
, data
y newData
, podemos acceder a cualquier ruta tal como existiría antes o después de un evento de escritura.
Considere este ejemplo, que permite operaciones de escritura siempre que el valor del nodo /allow_writes/
sea true
, el nodo principal no tenga un indicador de readOnly
establecido y haya un elemento secundario llamado foo
en los datos recién escritos:
".write": "root.child('allow_writes').val() === true && !data.parent().child('readOnly').exists() && newData.child('foo').exists()"
Validación de datos
La aplicación de las estructuras de datos y la validación del formato y el contenido de los datos deben realizarse mediante reglas .validate
, que se ejecutan solo después de que una regla .write
logra otorgar acceso. A continuación se muestra una definición de regla .validate
de muestra que solo permite fechas en el formato AAAA-MM-DD entre los años 1900-2099, que se verifica mediante una expresión regular.
".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])$/)"
Las reglas .validate
son el único tipo de regla de seguridad que no se conecta en cascada. Si alguna regla de validación falla en cualquier registro secundario, se rechazará toda la operación de escritura. Además, las definiciones de validación se ignoran cuando se eliminan los datos (es decir, cuando el nuevo valor que se escribe es null
).
Estos pueden parecer puntos triviales, pero de hecho son características importantes para escribir reglas de seguridad de base de datos en tiempo real de Firebase. Considere las siguientes reglas:
{ "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()" } } } }
Con esta variante en mente, observe los resultados de las siguientes operaciones de escritura:
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);
C objetivo
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];
Rápido
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);
DESCANSO
# 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
Ahora veamos la misma estructura, pero usando reglas .write
en lugar 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()" } } } }
En esta variante, cualquiera de las siguientes operaciones tendría éxito:
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);
C objetivo
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];
Rápido
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);
DESCANSO
# 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
Esto ilustra las diferencias entre las reglas .write
y .validate
. Como se demostró, todas estas reglas deben escribirse con .validate
, con la posible excepción de la regla newData.hasChildren()
, que dependería de si se deben permitir las eliminaciones.
Reglas basadas en consultas
Aunque no puede usar reglas como filtros , puede limitar el acceso a subconjuntos de datos usando parámetros de consulta en sus reglas. Usar query.
expresiones en sus reglas para otorgar acceso de lectura o escritura en función de los parámetros de consulta.
Por ejemplo, la siguiente regla basada en consultas utiliza reglas de seguridad basadas en usuarios y reglas basadas en consultas para restringir el acceso a los datos en la colección de baskets
solo a las cestas de la compra que posee el usuario activo:
"baskets": {
".read": "auth.uid !== null &&
query.orderByChild === 'owner' &&
query.equalTo === auth.uid" // restrict basket access to owner of basket
}
La siguiente consulta, que incluye los parámetros de consulta en la regla, se realizará correctamente:
db.ref("baskets").orderByChild("owner")
.equalTo(auth.currentUser.uid)
.on("value", cb) // Would succeed
Sin embargo, las consultas que no incluyen los parámetros en la regla fallarán con un error PermissionDenied
denegado:
db.ref("baskets").on("value", cb) // Would fail with PermissionDenied
También puede usar reglas basadas en consultas para limitar la cantidad de datos que descarga un cliente a través de operaciones de lectura.
Por ejemplo, la siguiente regla limita el acceso de lectura solo a los primeros 1000 resultados de una consulta, ordenados por prioridad:
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)
La siguiente query.
Las expresiones están disponibles en Reglas de seguridad de la base de datos en tiempo real.
Expresiones de regla basadas en consultas | ||
---|---|---|
Expresión | Escribe | Descripción |
consulta.orderByKey consulta.orderByPriority consulta.orderByValue | booleano | True para consultas ordenadas por clave, prioridad o valor. Falso en caso contrario. |
consulta.orderByChild | cuerda nulo | Utilice una cadena para representar la ruta relativa a un nodo secundario. Por ejemplo, query.orderByChild === "address/zip" . Si la consulta no está ordenada por un nodo secundario, este valor es nulo. |
consulta.startAt consulta.endAt consulta.igual a | cuerda número booleano nulo | Recupera los límites de la consulta en ejecución o devuelve un valor nulo si no hay un conjunto de límites. |
consulta.limitToFirst consulta.limitToLast | número nulo | Recupera el límite de la consulta en ejecución o devuelve un valor nulo si no hay un límite establecido. |
Próximos pasos
Después de esta discusión de las condiciones, tiene una comprensión más sofisticada de las Reglas y está listo para:
Aprenda a manejar los casos de uso principales y aprenda el flujo de trabajo para desarrollar, probar e implementar reglas:
- Obtenga información sobre el conjunto completo de variables de Reglas predefinidas que puede usar para crear condiciones .
- Escriba reglas que aborden escenarios comunes .
- Amplía tus conocimientos revisando situaciones en las que debes detectar y evitar Reglas inseguras .
- Obtén más información sobre Firebase Local Emulator Suite y cómo puedes usarlo para probar las reglas .
- Revise los métodos disponibles para implementar reglas .
Aprenda las características de las reglas que son específicas de Realtime Database:
- Aprenda a indexar su base de datos en tiempo real .
- Revise la API REST para implementar reglas .