Google 致力于为黑人社区推动种族平等。查看具体举措
此页面由 Cloud Translation API 翻译。
Switch to English

Utilizzare le condizioni nelle regole del database in tempo reale

Questa guida si basa sull'apprendimento della guida linguistica principale delle regole di sicurezza di Firebase per mostrare come aggiungere condizioni alle regole di sicurezza del database in tempo reale di Firebase.

L'elemento principale delle regole di sicurezza del database in tempo reale è 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 di valori letterali true e false come condizioni funziona perfettamente. Ma il linguaggio delle regole di sicurezza del database in tempo reale offre modi per scrivere condizioni più complesse che possono:

  • Controlla l'autenticazione dell'utente
  • Valuta i dati esistenti rispetto ai dati appena inviati
  • Accedi e confronta diverse parti del tuo database
  • Convalida i dati in arrivo
  • Utilizza la struttura delle query in arrivo per la logica di sicurezza

Utilizzo di variabili $ per acquisire segmenti di percorso

È possibile acquisire parti del percorso per una lettura o una scrittura dichiarando le variabili di acquisizione con il prefisso $ . Serve come carattere jolly e memorizza il valore di quella chiave per l'utilizzo 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 nomi di percorso costanti. In questo esempio, stiamo usando la variabile $other per dichiarare una regola .validate che garantisce che il widget non abbia elementi .validate diversi da title e color . Qualsiasi scrittura che comporterebbe la creazione di figli 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 modelli 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 solo agli utenti che hanno eseguito l'accesso di scrivere dati.

Se la tua 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 , vedere la documentazione di riferimento .

Firebase Authentication si integra con Firebase Realtime Database per consentirti di controllare l'accesso ai dati per utente in base alle condizioni. Una volta che un utente si autentica, la variabile auth nelle regole delle regole di sicurezza del database in tempo reale verrà popolata con le informazioni dell'utente. Queste informazioni includono il loro identificatore univoco ( uid ) e i dati dell'account collegato, come un ID Facebook o un indirizzo e-mail 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 Firebase Realtime Database Security Rules con le informazioni di autenticazione sui tuoi utenti. Combinando questi due concetti, è possibile controllare l'accesso ai dati in base all'identità dell'utente.

La variabile auth

La variabile di auth predefinita nelle regole è nulla prima che abbia luogo l'autenticazione.

Una volta che un utente è stato autenticato 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 unico in tutti i provider.
gettone Il contenuto del token ID autenticazione Firebase. Vedere la documentazione di riferimento per auth.token per ulteriori dettagli.

Di seguito è riportato un esempio di regola che utilizza la variabile auth per garantire che ogni utente possa scrivere solo su 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"
      }
    }
  }
}

Strutturare il database per supportare le condizioni di autenticazione

Di solito è utile strutturare il database in un modo che semplifichi la scrittura delle regole. Un modello comune per l'archiviazione dei dati utente nel database in tempo reale consiste nell'archiviare tutti gli utenti in un singolo nodo users cui figli sono i valori uid per ogni utente. Se desideri limitare l'accesso a questi dati in modo tale che solo l'utente che ha effettuato l'accesso possa vedere i propri dati, le tue regole sarebbero simili a queste.

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

Utilizzo delle attestazioni personalizzate di autenticazione

Per le app che richiedono un controllo di accesso personalizzato per utenti diversi, Firebase Authentication consente agli sviluppatori di impostare rivendicazioni su un utente Firebase . Queste attestazioni sono accessibili nella variabile auth.token nelle regole. Di seguito è riportato un esempio di regole che utilizzano l' hasEmergencyTowel 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 attestazioni a questi token. Queste attestazioni sono disponibili sulla variabile auth.token nelle regole.

Dati esistenti e nuovi dati

La variabile di 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 unito dei nuovi dati in fase di scrittura 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()"

Riferimento a 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 come sarebbe prima o dopo un evento di scrittura.

Considera questo esempio, che consente operazioni di scrittura fintanto che il valore del nodo /allow_writes/ è true , il nodo genitore non ha un flag readOnly impostato e c'è un figlio chiamato 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 del contenuto dei dati devono essere eseguite utilizzando .validate regole .validate , che vengono eseguite solo dopo che una regola .write riesce a concedere l'accesso. Di seguito è riportata una definizione di regola .validate esempio che consente solo le date nel formato AAAA-MM-GG tra gli anni 1900-2099, che vengono verificate 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 si .validate a cascata. Se una qualsiasi regola di convalida non riesce su un record figlio, l'intera operazione di scrittura verrà rifiutata. Inoltre, le definizioni di convalida vengono ignorate quando i dati vengono eliminati (ovvero, quando il nuovo valore da scrivere è null ).

Questi potrebbero sembrare punti banali, ma in realtà sono funzionalità significative per la scrittura di potenti regole di sicurezza del database in tempo reale Firebase. 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()"
      }
    }
  }
}

Con questa variante in mente, guarda i risultati per le 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);
Obiettivo-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);
Giava
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);
RIPOSO
# 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 diamo un'occhiata alla stessa struttura, ma usando .write regole .write invece di .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 delle seguenti operazioni avrebbe successo:

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);
Obiettivo-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)
Giava
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);
RIPOSO
# 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 dovrebbero essere scritte usando .validate , con la possibile eccezione della regola newData.hasChildren() , che dipenderà dal fatto che le cancellazioni debbano essere consentite.

Regole basate su query

Sebbene non sia possibile utilizzare le regole come filtri , è possibile limitare l'accesso a sottoinsiemi di dati utilizzando i parametri di query nelle regole. Usa query. espressioni 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 sull'utente e regole basate su query per limitare l'accesso ai dati nella raccolta dei baskets solo ai carrelli della spesa posseduti dall'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 della query nella regola, avrà esito positivo:

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

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

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

È inoltre possibile utilizzare regole basate su query per limitare la quantità di dati che un client scarica tramite operazioni di lettura.

Ad esempio, la regola seguente limita l'accesso in lettura solo ai primi 1000 risultati di una query, in base alla 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)

La seguente query. le espressioni sono disponibili nelle regole di sicurezza del database in tempo reale.

Espressioni di regole basate su query
Espressione genere Descrizione
query.orderByKey
query.orderByPriority
query.orderByValue
booleano Vero per le query ordinate per chiave, priorità o valore. Altrimenti falso.
query.orderByChild corda
nullo
Usa una stringa per rappresentare il percorso relativo a un nodo figlio. Ad esempio, query.orderByChild == "address/zip" . Se la query non è ordinata da un nodo figlio, questo valore è null.
query.startAt
query.endAt
query.equalTo
corda
numero
booleano
nullo
Recupera i limiti della query in esecuzione o restituisce null se non è presente alcun set di limiti.
query.limitToFirst
query.limitToLast
numero
nullo
Recupera il limite sulla query in esecuzione o restituisce null se non è stato impostato alcun limite.

Prossimi passi

Dopo questa discussione sulle condizioni, hai una comprensione più sofisticata delle regole e sei pronto per:

Scopri come gestire i casi d'uso principali e apprendi il flusso di lavoro per lo sviluppo, il test e la distribuzione delle regole:

Scopri le funzionalità delle regole specifiche per Realtime Database: