Questa guida si basa sulla guida Impara il linguaggio di base delle regole di sicurezza Firebase per mostrare come aggiungere condizioni alle regole di sicurezza di Firebase Realtime Database.
L'elemento di base principale delle regole di sicurezza di Realtime Database è la condizione. Una
condizione è un'espressione booleana che determina se una particolare operazione
deve essere consentita o negata. Per le regole di base, l'utilizzo dei valori letterali true
e false
come condizioni funziona perfettamente. Tuttavia, il linguaggio delle regole di sicurezza di Realtime Database ti offre
modi per scrivere condizioni più complesse che possono:
- Controllare l'autenticazione utente
- Valutare i dati esistenti rispetto a quelli appena inviati
- Accedere e confrontare diverse parti del database
- Convalidare i dati in entrata
- Utilizzare la struttura delle query in entrata per la logica di sicurezza
Utilizzo delle variabili $ per acquisire segmenti di percorso
Puoi acquisire porzioni 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 da utilizzare all'interno
delle 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 i nomi dei percorsi costanti. In questo esempio, utilizziamo la variabile $other
per dichiarare
una regola .validate
che garantisce che
widget
non abbia elementi secondari diversi da title
e color
.
Qualsiasi scrittura che comporterebbe la creazione di ulteriori figli non riuscirebbe.
{ "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 allo stato di autenticazione dell'utente. Ad esempio, la tua app potrebbe voler consentire la scrittura di dati solo agli utenti che hanno eseguito l'accesso.
Se la tua app utilizza Firebase Authentication, la variabile request.auth
contiene
le informazioni di autenticazione per il client che richiede i dati.
Per maggiori 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
in base all'utente utilizzando le condizioni. Una volta che un utente si autentica, la variabile auth
nelle regole di sicurezza di Realtime Database viene compilata con le informazioni dell'utente. Queste informazioni includono il loro identificatore univoco (uid
)
nonché i dati dell'account collegato, 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 dei tuoi utenti. Combinando questi due concetti, puoi controllare l'accesso ai dati in base all'identità dell'utente.
Variabile auth
La variabile auth
predefinita nelle regole è nulla prima
dell'autenticazione.
Una volta autenticato un utente con Firebase Authentication, conterrà i seguenti attributi:
provider | Il metodo di autenticazione utilizzato ("password", "anonymous", "facebook", "github", "google", o "twitter"). |
uid | Un ID utente univoco, garantito per essere univoco in tutti i provider. |
token |
I contenuti del token ID Firebase Auth. Per ulteriori dettagli, consulta la documentazione di riferimento per
auth.token .
|
Ecco una regola di esempio che utilizza la variabile auth
per garantire che
ogni utente possa scrivere solo in un percorso specifico per l'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" } } } }
Strutturare il database per supportare le condizioni di autenticazione
In genere è utile strutturare il database in modo da semplificare la scrittura di Rules. Un pattern comune per l'archiviazione dei dati utente in Realtime Database consiste
nell'archiviare tutti gli utenti in un singolo nodo users
i cui figli sono
i valori uid
per ogni utente. Se volessi limitare l'accesso a questi dati in modo che solo l'utente che ha eseguito l'accesso possa vedere i propri dati, le regole
sarebbero simili a queste.
{ "rules": { "users": { "$uid": { ".read": "auth !== null && auth.uid === $uid" } } } }
Utilizzo di rivendicazioni personalizzate di autenticazione
Per le app che richiedono un controllo dell'accesso personalizzato per utenti diversi, Firebase Authentication
consente agli sviluppatori di impostare attestazioni su un utente Firebase.
Queste rivendicazioni sono accessibili nella variabileauth.token
delle regole.
Ecco un esempio di regole che utilizzano l'attestazione 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 token di autenticazione personalizzati
propri possono facoltativamente aggiungere rivendicazioni a questi token. Queste
rivendicazioni sono disponibili nella variabile auth.token
delle regole.
Dati esistenti e nuovi dati
La variabile predefinita data
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 unito dei nuovi dati scritti
e dei dati esistenti.
Per illustrare, questa regola ci consentirebbe di creare nuovi record o eliminare quelli esistenti, ma non di apportare modifiche ai dati esistenti non nulli:
// 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 le variabili
predefinite root
, data
e newData
, possiamo
accedere a qualsiasi percorso così come esisterebbe prima o dopo un evento di scrittura.
Considera questo esempio, che consente operazioni di scrittura a condizione che il valore del nodo /allow_writes/
sia true
, che il nodo principale non abbia un flag readOnly
impostato e che nei dati appena scritti sia presente un nodo secondario denominato foo
:
".write": "root.child('allow_writes').val() === true && !data.parent().child('readOnly').exists() && newData.child('foo').exists()"
Convalida dei dati
L'applicazione delle strutture dei dati e la convalida del formato e del contenuto dei dati devono
essere eseguite utilizzando le regole .validate
, che vengono eseguite solo dopo che una regola
.write
ha avuto esito positivo per concedere l'accesso. Di seguito è riportata una definizione di regola .validate
di esempio che consente solo date nel formato AAAA-MM-GG tra gli anni 1900 e 2099, che viene verificata 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 regola di sicurezza che non viene applicata a cascata. Se una regola di convalida non viene rispettata 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
che viene scritto è null
).
Questi potrebbero sembrare punti banali, ma in realtà sono funzionalità importanti per scrivere regole di sicurezza di Firebase Realtime Database efficaci. Considera 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
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
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
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
Questo 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 ricerca nella regola, andrebbe a buon fine:
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 andrebbero a buon fine e restituirebbero
un errore PermissionDenied
:
db.ref("baskets").on("value", cb) // Would fail with PermissionDenied
Puoi anche utilizzare regole basate su query per limitare la quantità di dati scaricati da un client tramite operazioni di lettura.
Ad esempio, la seguente regola limita l'accesso in lettura solo ai primi 1000 risultati di una query, in base all'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 | ||
---|---|---|
Expression | Tipo | Descrizione |
query.orderByKey query.orderByPriority query.orderByValue |
booleano | Vero 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 è
ordinata in base a un nodo secondario, questo valore è null.
|
query.startAt query.endAt query.equalTo |
stringa numero booleano null |
Recupera i limiti della query in esecuzione o restituisce null se non è impostato alcun limite. |
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, avrai una comprensione più sofisticata di Rules e sarai pronto a:
Scopri come gestire i casi d'uso principali e il flusso di lavoro per lo sviluppo, il test e il deployment di Rules:
- Scopri l'insieme completo di Rules variabili predefinite che puoi utilizzare per creare condizioni.
- Scrivi regole che riguardano scenari comuni.
- Approfondisci le tue conoscenze esaminando le situazioni in cui devi individuare ed evitare regole non sicure.
- Scopri di più su Firebase Local Emulator Suite e su come utilizzarlo per testare Rules.
- Esamina i metodi disponibili per il deployment di Rules.
Scopri le funzionalità di Rules specifiche per Realtime Database:
- Scopri come indicizzare il tuo Realtime Database.
- Esamina l'API REST per il deployment di Rules.