Utilizzare le condizioni nelle regole di sicurezza di Realtime Database

Questa guida si basa sulla guida Scopri il linguaggio di base delle regole di sicurezza Firebase per mostrare come aggiungere condizioni alle regole di sicurezza di Firebase Realtime Database.

Il componente di base principale delle regole di sicurezza di Realtime Database è la condizione. R è un'espressione booleana che determina se una determinata operazione deve essere consentito o negato. Per le regole di base, l'utilizzo di letterali true e false come condizioni funziona perfettamente. Ma il linguaggio delle regole di sicurezza di Realtime Database offre scrivere condizioni più complesse che possono:

  • Controllare l'autenticazione degli utenti
  • Valuta i dati esistenti in base a quelli appena inviati
  • Accedere e confrontare parti diverse del database
  • Convalida i dati in entrata
  • Utilizza la struttura delle query in entrata per la logica di sicurezza

Utilizzare le variabili $ per acquisire i segmenti di percorso

Puoi acquisire parti del percorso per una lettura o una scrittura dichiarando variabili di acquisizione con il prefisso $. Funge da carattere jolly e memorizza il valore della chiave per utilizzarlo all'interno condizioni delle regole:

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

Le variabili $ dinamiche possono essere utilizzate anche in parallelo con il percorso costante i nomi degli utenti. In questo esempio, utilizziamo la variabile $other per dichiarare una regola .validate che garantisce che widget non ha figli diversi da title e color. Qualsiasi scrittura che potrebbe comportare la creazione di elementi figlio aggiuntivi non andrà a buon fine.

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

Autenticazione

Uno dei pattern di regole di sicurezza più comuni è il controllo dell'accesso in base lo stato dell'autenticazione dell'utente. Ad esempio, l'app potrebbe voler consentire gli utenti che hanno eseguito l'accesso per scrivere dati.

Se l'app utilizza Firebase Authentication, la variabile request.auth contiene le informazioni di autenticazione per il client che richiede i dati. Per ulteriori informazioni su request.auth, consulta la documentazione di riferimento.

Firebase Authentication si integra con Firebase Realtime Database per consentirti di controllare l'accesso ai dati su base utente utilizzando le condizioni. Una volta autenticato un utente, la variabile auth nelle regole di sicurezza di Realtime Database verrà compilata con le informazioni dell'utente. Queste informazioni includono l'identificatore univoco (uid), nonché i dati dell'account collegati, come un ID Facebook o un indirizzo email, e altre informazioni. Se implementi un provider di autenticazione personalizzato, puoi aggiungere i tuoi campi al payload di autenticazione dell'utente.

Questa sezione spiega come combinare il linguaggio delle regole di sicurezza di Firebase Realtime Database con le informazioni di autenticazione degli utenti. Combinando questi due concetti, puoi controllare l'accesso ai dati in base all'identità dell'utente.

La variabile auth

La variabile predefinita auth nelle regole è nulla prima avviene l'autenticazione.

Una volta che l'utente è stato autenticato con Firebase Authentication conterrà i seguenti attributi:

fornitore Il metodo di autenticazione utilizzato ("password", "anonima", "facebook", "github", "google", o "twitter").
uid Un ID utente unico, la cui univocità è garantita tra tutti i provider.
token I contenuti del token ID di Firebase Auth. Per ulteriori dettagli, consulta la documentazione di riferimento per auth.token.

Ecco un esempio di regola che utilizza la variabile auth per garantire che: ogni utente può scrivere solo in un percorso specifico dell'utente:

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

Strutturazione del database per supportare le condizioni di autenticazione

In genere è utile strutturare il database in modo da semplificare la scrittura.Rules Un pattern comune per l'archiviazione dei dati utente in Realtime Database è per archiviare tutti i tuoi utenti in un singolo nodo users i cui figli sono i valori uid per ogni utente. Se vuoi limitare l'accesso a questi dati in modo che solo l'utente che ha eseguito l'accesso possa vedere i propri dati, le regole potrebbe essere simile a questo.

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

Utilizzo delle attestazioni personalizzate di autenticazione

Per le app che richiedono un controllo dell'accesso personalizzato per utenti diversi, Firebase Authentication consente agli sviluppatori di impostare rivendicazioni su un utente Firebase. Questi claim sono accessibili nella variabileauth.token delle regole. Ecco un esempio di regole che utilizzano l'affermazione personalizzata hasEmergencyTowel:

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

Gli sviluppatori che creano i propri token di autenticazione personalizzati possono, facoltativamente, aggiungere rivendicazioni a questi token. Questi claim sono disponibili nella variabile auth.token nelle regole.

Dati esistenti e dati nuovi

La variabile data predefinita viene utilizzata per fare riferimento ai dati prima che venga eseguita un'operazione di scrittura. Al contrario, la variabile newData contiene i nuovi dati che esisteranno se l'operazione di scrittura ha esito positivo. newData rappresenta il risultato dell'unione dei nuovi dati in fase di scrittura e dei dati esistenti.

Per spiegarci meglio, questa regola consente di creare nuovi record o eliminare record esistenti ma non per apportare modifiche ai dati non null esistenti:

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

Fare riferimento ai dati in altri percorsi

Qualsiasi dato può essere utilizzato come criterio per le regole. Utilizzando i modelli le variabili root, data e newData, può accedere a qualsiasi percorso così come esisterebbe prima o dopo un evento di scrittura.

Prendi in considerazione questo esempio, che consente operazioni di scrittura purché il valore del node /allow_writes/ sia true, il node principale non abbia un flag readOnly impostato e sia presente un node secondario denominato foo nei dati appena scritti:

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

Convalida dei dati

L'applicazione delle strutture di dati e la convalida del formato e dei contenuti dei dati devono essere eseguite utilizzando le regole .validate, che vengono eseguite solo dopo che una regola .write è riuscita a concedere l'accesso. Di seguito è riportato un esempio Definizione della regola .validate che consente solo date nel formato AAAA-MM-GG tra gli anni 1900-2099, controllato utilizzando un'espressione regolare.

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

Le regole .validate sono l'unico tipo di regole di sicurezza che non si applicano in modo ricorsivo. Se una qualsiasi regola di convalida non va a buon fine in un record secondario, l'intera operazione di scrittura verrà rifiutata. Inoltre, le definizioni di convalida vengono ignorate quando i dati vengono eliminati (ovvero quando il nuovo valore scritto è null).

Potrebbero sembrare semplici, ma sono in realtà caratteristiche significative per scrivere le potenti regole di sicurezza di Firebase Realtime Database. Tieni presente le seguenti regole:

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

Tenendo presente questa variante, esamina i risultati delle seguenti operazioni di scrittura:

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
Nota: questo prodotto Firebase non è disponibile come target di App Clip.
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
Nota: questo prodotto Firebase non è disponibile nel target dell'app clip.
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

Ora esaminiamo la stessa struttura, ma utilizzando le regole .write anziché .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()"
      }
    }
  }
}

In questa variante, una qualsiasi delle seguenti operazioni andrebbe a buon fine:

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
Nota: questo prodotto Firebase non è disponibile come target di App Clip.
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
Nota: questo prodotto Firebase non è disponibile nel target dell'app clip.
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

Illustra le differenze tra le regole .write e .validate. Come dimostrato, tutte queste regole devono essere scritte utilizzando .validate, con la possibile eccezione della regola newData.hasChildren(), che dipende dal fatto che le eliminazioni debbano essere consentite.

Regole basate su query

Anche se non puoi utilizzare le regole come filtri, Puoi limitare l'accesso a sottoinsiemi di dati utilizzando i parametri di query nelle regole. Utilizza le espressioni query. nelle regole per concedere l'accesso in lettura o scrittura in base ai parametri di query.

Ad esempio, la seguente regola basata su query utilizza regole di sicurezza basate sugli utenti e regole basate su query per limitare l'accesso ai dati nella raccolta baskets solo ai carrelli degli acquisti di proprietà dell'utente attivo:

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

La seguente query, che include i parametri di query nella regola, operazione riuscita:

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

Tuttavia, le query che non includono i parametri nella regola non riusciranno con un errore PermissionDenied:

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

Puoi anche utilizzare le regole basate su query per limitare la quantità di dati scaricati da un client mediante operazioni di lettura.

Ad esempio, la seguente regola limita l'accesso in lettura solo alle prime 1000 risultati di una query, in ordine di 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)

Le seguenti espressioni query. sono disponibili nelle regole di sicurezza di Realtime Database.

Espressioni di regole basate su query
Espressione Tipo Descrizione
query.orderByKey
query.orderByPriority
query.orderByValue
booleano True per le query ordinate per chiave, priorità o valore. Falso negli altri casi.
query.orderByChild string
null
Utilizza una stringa per rappresentare il percorso relativo a un nodo secondario. Ad esempio, query.orderByChild === "address/zip". Se la query non è ordinato da un nodo figlio, questo valore è nullo.
query.startAt
query.endAt
query.equalTo
stringa
numero
booleano
null
Recupera i limiti della query in esecuzione o restituisce un valore nullo se sono presenti non ha limiti impostati.
query.limitToFirst
query.limitToLast
number
null
Recupera il limite per la query in esecuzione o restituisce null se non è impostato alcun limite.

Passaggi successivi

Dopo questa discussione sulle condizioni, hai una conoscenza più approfondita di Rules e puoi:

Scopri come gestire i casi d'uso principali e il flusso di lavoro per lo sviluppo, la verifica e il deployment di Rules:

Scopri le funzionalità di Rules specifiche di Realtime Database: