La sicurezza può essere uno dei componenti più complessi del puzzle dello sviluppo di app. Nella maggior parte delle applicazioni, gli sviluppatori devono creare ed eseguire un server che gestisca l'autenticazione (l'identità di un utente) e l'autorizzazione (ciò che un utente può fare).
Firebase Security Rules rimuove il livello intermedio (server) e ti consente di specificare autorizzazioni basate sul percorso per i client che si connettono direttamente ai tuoi dati. Consulta questa guida per scoprire di più su come vengono applicate le regole alle richieste in arrivo.
Seleziona un prodotto per scoprire di più sulle relative regole.
Cloud Firestore
Struttura di base
Firebase Security Rules in Cloud Firestore e Cloud Storage utilizza la seguente struttura e sintassi:
service <<name>> {
// Match the resource path.
match <<path>> {
// Allow the request if the following conditions are true.
allow <<methods>> : if <<condition>>
}
}
Quando crei le regole, è importante comprendere i seguenti concetti chiave:
- Richiesta: il metodo o i metodi invocati nell'istruzione
allow
. Si tratta di metodi che consenti di eseguire. I metodi standard sono:get
,list
,create
,update
edelete
. I metodi di utilitàread
ewrite
consentono un accesso in lettura e scrittura ampio al database o al percorso di archiviazione specificato. - Percorso: il database o la posizione di archiviazione, rappresentata come percorso URI.
- Regola: l'istruzione
allow
, che include una condizione che consente una richiesta se ha valore true.
Versione 2 delle regole di sicurezza
Da maggio 2019 è disponibile la versione 2 delle regole di sicurezza Firebase. La versione 2 delle regole cambia il comportamento dei caratteri jolly ricorsivi {name=**}
. Devi utilizzare la versione 2 se prevedi di utilizzare le query sui gruppi di raccolte. Devi attivare la versione 2 impostando rules_version = '2';
come prima riga delle regole di sicurezza:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
Percorsi corrispondenti
Tutte le istruzioni di corrispondenza devono puntare a documenti, non a raccolte. Un'istruzione di corrispondenza può fare riferimento a un documento specifico, come in match /cities/SF
, oppure utilizzare caratteri jolly per fare riferimento a qualsiasi documento nel percorso specificato, come in match /cities/{city}
.
Nell'esempio precedente, l'istruzione match utilizza la sintassi dei caratteri jolly {city}
.
Ciò significa che la regola si applica a qualsiasi documento della raccolta cities
, ad esempio /cities/SF
o /cities/NYC
. Quando le espressioni allow
nell'istruzione di corrispondenza vengono
valutate, la variabile city
viene risolta nel nome del documento della città,
ad esempio SF
o NYC
.
Sottocollezioni corrispondenti
I dati in Cloud Firestore sono organizzati in raccolte di documenti e ogni documento può estendere la gerarchia tramite sottocollezioni. È importante comprendere come le regole di sicurezza interagiscono con i dati gerarchici.
Considera la situazione in cui ogni documento della raccolta cities
contiene una raccolta secondaria landmarks
. Le regole di sicurezza si applicano solo al percorso corrispondente, pertanto i controlli di accesso definiti nella raccolta cities
non si applicano alla sottoraccolta landmarks
. Scrivi invece regole esplicite per controllare l'accesso alle sottocollezioni:
service cloud.firestore {
match /databases/{database}/documents {
match /cities/{city} {
allow read, write: if <condition>;
// Explicitly define rules for the 'landmarks' subcollection
match /landmarks/{landmark} {
allow read, write: if <condition>;
}
}
}
}
Quando nidifichi le istruzioni match
, il percorso dell'istruzione match
interna è sempre relativo al percorso dell'istruzione match
esterna. Pertanto, i seguenti set di regole
sono equivalenti:
service cloud.firestore {
match /databases/{database}/documents {
match /cities/{city} {
match /landmarks/{landmark} {
allow read, write: if <condition>;
}
}
}
}
service cloud.firestore {
match /databases/{database}/documents {
match /cities/{city}/landmarks/{landmark} {
allow read, write: if <condition>;
}
}
}
Caratteri jolly ricorsivi
Se vuoi che le regole vengano applicate a una gerarchia arbitrariamente profonda, utilizza la sintassi del carattere jolly ricorsivo {name=**}
:
service cloud.firestore {
match /databases/{database}/documents {
// Matches any document in the cities collection as well as any document
// in a subcollection.
match /cities/{document=**} {
allow read, write: if <condition>;
}
}
}
Quando utilizzi la sintassi dei caratteri jolly ricorsivi, la variabile jolly conterrà l'intero segmento di percorso corrispondente, anche se il documento si trova in una sottoraccolta nidificata in modo complesso. Ad esempio, le regole elencate sopra corrispondono a un documento situato in /cities/SF/landmarks/coit_tower
e il valore della variabile document
è SF/landmarks/coit_tower
.
Tieni presente, tuttavia, che il comportamento dei caratteri jolly ricorsivi dipende dalla versione delle regole.
Versione 1
Per impostazione predefinita, le regole di sicurezza utilizzano la versione 1. Nella versione 1, i caratteri jolly ricorsivi
corrispondono a uno o più elementi del percorso. Non corrispondono a un percorso vuoto, quindi
match /cities/{city}/{document=**}
corrisponde ai documenti nelle raccolte secondarie, ma
non nella raccolta cities
, mentre match /cities/{document=**}
corrisponde
a entrambi i documenti nelle raccolte cities
e nelle raccolte secondarie.
I caratteri jolly ricorsivi devono trovarsi alla fine di un'istruzione di corrispondenza.
Versione 2
Nella versione 2 delle regole di sicurezza, i caratteri jolly ricorsivi corrispondono a zero o più elementi del percorso. match/cities/{city}/{document=**}
corrisponde ai documenti di qualsiasi
sottoraccolta, nonché ai documenti della raccolta cities
.
Devi attivare la versione 2 aggiungendo rules_version = '2';
nella parte superiore delle tue regole di sicurezza:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Matches any document in the cities collection as well as any document
// in a subcollection.
match /cities/{city}/{document=**} {
allow read, write: if <condition>;
}
}
}
Puoi avere al massimo un carattere jolly ricorsivo per istruzione di corrispondenza, ma nella versione 2 puoi posizionarlo in qualsiasi punto dell'istruzione di corrispondenza. Ad esempio:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Matches any document in the songs collection group
match /{path=**}/songs/{song} {
allow read, write: if <condition>;
}
}
}
Se utilizzi le query sui gruppi di raccolte, devi utilizzare la versione 2. Consulta la sezione Proteggere le query sui gruppi di raccolte.
Istruzioni di corrispondenza sovrapposte
È possibile che un documento corrisponda a più di un'istruzione match
. Nel caso in cui più espressioni allow
corrispondano a una richiesta, l'accesso è consentito se una delle condizioni è true
:
service cloud.firestore {
match /databases/{database}/documents {
// Matches any document in the 'cities' collection.
match /cities/{city} {
allow read, write: if false;
}
// Matches any document in the 'cities' collection or subcollections.
match /cities/{document=**} {
allow read, write: if true;
}
}
}
Nell'esempio precedente, tutte le letture e le scritture nella raccolta cities
saranno consentite perché la seconda regola è sempre true
, anche se la prima regola è sempre false
.
Limiti delle regole di sicurezza
Quando utilizzi le regole di sicurezza, tieni presente i seguenti limiti:
Limite | Dettagli |
---|---|
Numero massimo di chiamate exists() , get() e getAfter() per richiesta |
Il superamento di uno dei limiti comporta un errore di autorizzazione negata. Alcune chiamate di accesso ai documenti possono essere memorizzate nella cache e le chiamate nella cache non vengono considerate ai fini dei limiti. |
Profondità massima delle istruzioni match nidificate |
10 |
Lunghezza massima del percorso, in segmenti di percorso, consentita all'interno di un set di istruzioni match nidificate |
100 |
Numero massimo di variabili di acquisizione percorso consentite all'interno di un set di istruzioni match nidificate |
20 |
Profondità massima delle chiamate funzione | 20 |
Numero massimo di argomenti di funzione | 7 |
Numero massimo di associazioni di variabili let per funzione |
10 |
Numero massimo di chiamate di funzione ricorsive o cicliche | 0 (non consentite) |
Numero massimo di espressioni valutate per richiesta | 1000 |
Dimensione massima di un set di regole | I set di regole devono rispettare due limiti di dimensione:
|
Cloud Storage
Struttura di base
Firebase Security Rules in Cloud Firestore e Cloud Storage utilizza la seguente struttura e sintassi:
service <<name>> {
// Match the resource path.
match <<path>> {
// Allow the request if the following conditions are true.
allow <<methods>> : if <<condition>>
}
}
Quando crei le regole, è importante comprendere i seguenti concetti chiave:
- Richiesta: il metodo o i metodi invocati nell'istruzione
allow
. Si tratta di metodi che consenti di eseguire. I metodi standard sono:get
,list
,create
,update
edelete
. I metodi di utilitàread
ewrite
consentono un accesso in lettura e scrittura ampio al database o al percorso di archiviazione specificato. - Percorso: il database o la posizione di archiviazione, rappresentata come percorso URI.
- Regola: l'istruzione
allow
, che include una condizione che consente una richiesta se ha valore true.
Percorsi corrispondenti
Cloud Storage Security Rules match
i percorsi dei file utilizzati per accedere ai file in
Cloud Storage. Le regole possono match
includere percorsi esatti o con caratteri jolly e possono anche essere nidificate. Se nessuna regola di corrispondenza consente un metodo di richiesta o se la condizione è valutata come false
, la richiesta viene rifiutata.
Corrispondenze esatte
// Exact match for "images/profilePhoto.png" match /images/profilePhoto.png { allow write: if <condition>; } // Exact match for "images/croppedProfilePhoto.png" match /images/croppedProfilePhoto.png { allow write: if <other_condition>; }
Corrispondenze nidificate
// Partial match for files that start with "images" match /images { // Exact match for "images/profilePhoto.png" match /profilePhoto.png { allow write: if <condition>; } // Exact match for "images/croppedProfilePhoto.png" match /croppedProfilePhoto.png { allow write: if <other_condition>; } }
Corrispondenze con caratteri jolly
Le regole possono essere utilizzate anche per match
un pattern utilizzando i caratteri jolly. Un carattere jolly è una variabile imendata che rappresenta una singola stringa, ad esempio profilePhoto.png
, o più segmenti di percorso, ad esempio images/profilePhoto.png
.
Un carattere jolly viene creato aggiungendo parentesi graffe al nome del carattere jolly, ad esempio
{string}
. Un carattere jolly per più segmenti può essere dichiarato aggiungendo =**
al nome del carattere jolly, ad esempio {path=**}
:
// Partial match for files that start with "images" match /images { // Exact match for "images/*" // e.g. images/profilePhoto.png is matched match /{imageId} { // This rule only matches a single path segment (*) // imageId is a string that contains the specific segment matched allow read: if <condition>; } // Exact match for "images/**" // e.g. images/users/user:12345/profilePhoto.png is matched // images/profilePhoto.png is also matched! match /{allImages=**} { // This rule matches one or more path segments (**) // allImages is a path that contains all segments matched allow read: if <other_condition>; } }
Se più regole corrispondono a un file, il risultato è il OR
del risultato di tutte le valutazioni delle regole. In altre parole, se una regola a cui corrisponde il file restituisce true
, il risultato è true
.
Nelle regole precedenti, il file "images/profilePhoto.png" può essere letto se condition
o other_condition
valutano true, mentre il file "images/users/user:12345/profilePhoto.png" è soggetto solo al risultato di other_condition
.
È possibile fare riferimento a una variabile jolly all'interno dell'autorizzazione del nome o del percorso del file fornito in match
:
// Another way to restrict the name of a file match /images/{imageId} { allow read: if imageId == "profilePhoto.png"; }
Cloud Storage Security Rules non si applicano in modo ricorsivo e le regole vengono valutate solo quando il percorso della richiesta corrisponde a un percorso con regole specificate.
Richiedi valutazione
I caricamenti, i download, le modifiche e le eliminazioni dei metadati vengono valutati utilizzando il
request
inviato a Cloud Storage. La variabile request
contiene il percorso del file in cui viene eseguita la richiesta, l'ora in cui la richiesta viene ricevuta e il nuovo valore resource
se la richiesta è di scrittura. Sono inclusi anche le intestazioni HTTP
e lo stato di autenticazione.
L'oggetto request
contiene anche l'ID univoco dell'utente e il payload Firebase Authentication nell'oggetto request.auth
, che verrà spiegato ulteriormente nella sezione Autenticazione della documentazione.
Di seguito è riportato un elenco completo delle proprietà dell'oggetto request
:
Proprietà | Tipo | Descrizione |
---|---|---|
auth |
map<string, string> | Quando un utente ha eseguito l'accesso, fornisce uid , l'ID univoco dell'utente, e
token , una mappa di attestazioni JWT Firebase Authentication. In caso contrario, sarà
null . |
params |
map<string, string> | Mappa contenente i parametri di query della richiesta. |
path |
percorso | Un path che rappresenta il percorso in cui viene eseguita la richiesta. |
resource |
map<string, string> | Il nuovo valore della risorsa, presente solo nelle richieste write .
|
time |
timestamp | Un timestamp che rappresenta l'ora del server in cui viene valutata la richiesta. |
Valutazione delle risorse
Quando valuti le regole, ti consigliamo di valutare anche i metadati del file caricato, scaricato, modificato o eliminato. In questo modo, puoi creare regole complesse e potenti che, ad esempio, consentono di caricare solo file con determinati tipi di contenuti o di eliminare solo file di dimensioni superiori a una determinata.
Firebase Security Rules per Cloud Storage fornisce i metadati del file nell'oggetto resource
, che contiene coppie chiave/valore dei metadati visualizzati in un oggetto Cloud Storage. Queste proprietà possono essere controllate nelle richieste read
o
write
per garantire l'integrità dei dati.
Nelle richieste write
(come caricamenti, aggiornamenti dei metadati ed eliminazioni), oltre all'oggetto resource
, che contiene i metadati del file attualmente esistente nel percorso della richiesta, puoi anche utilizzare l'oggetto request.resource
, che contiene un sottoinsieme dei metadati del file da scrivere se la scrittura è consentita. Puoi utilizzare questi due valori per garantire l'integrità dei dati o applicare vincoli di applicazione come il tipo o le dimensioni del file.
Di seguito è riportato un elenco completo delle proprietà dell'oggetto resource
:
Proprietà | Tipo | Descrizione |
---|---|---|
name |
stringa | Il nome completo dell'oggetto |
bucket |
stringa | Il nome del bucket in cui si trova questo oggetto. |
generation |
int | La Google Cloud Storage generazione di oggetti di questo oggetto. |
metageneration |
int | La Google Cloud Storage metagenerazione dell'oggetto di questo oggetto. |
size |
int | Le dimensioni dell'oggetto in byte. |
timeCreated |
timestamp | Un timestamp che rappresenta l'ora di creazione di un oggetto. |
updated |
timestamp | Un timestamp che rappresenta l'ora dell'ultimo aggiornamento di un oggetto. |
md5Hash |
stringa | Un hash MD5 dell'oggetto. |
crc32c |
stringa | Un hash crc32c dell'oggetto. |
etag |
stringa | L'etag associato a questo oggetto. |
contentDisposition |
stringa | La disposizione dei contenuti associata a questo oggetto. |
contentEncoding |
stringa | La codifica dei contenuti associata a questo oggetto. |
contentLanguage |
stringa | La lingua dei contenuti associata a questo oggetto. |
contentType |
stringa | Il tipo di contenuti associato a questo oggetto. |
metadata |
map<string, string> | Coppie chiave/valore di metadati personalizzati aggiuntivi specificati dallo sviluppatore. |
request.resource
li contiene tutti, ad eccezione di generation
, metageneration
, etag
, timeCreated
e updated
.
Limiti delle regole di sicurezza
Quando utilizzi le regole di sicurezza, tieni presente i seguenti limiti:
Limite | Dettagli |
---|---|
Numero massimo di chiamate firestore.exists() e
firestore.get() per richiesta |
2 per richieste di documenti singoli e di query. Il superamento di questo limite comporta un errore di autorizzazione negata. Le chiamate di accesso agli stessi documenti possono essere memorizzate nella cache e le chiamate nella cache non vengono considerate ai fini dei limiti. |
Esempio completo
Mettendo tutto insieme, puoi creare un esempio completo di regole per una soluzione di archiviazione delle immagini:
service firebase.storage { match /b/{bucket}/o { match /images { // Cascade read to any image type at any path match /{allImages=**} { allow read; } // Allow write files to the path "images/*", subject to the constraints: // 1) File is less than 5MB // 2) Content type is an image // 3) Uploaded content type matches existing content type // 4) File name (stored in imageId wildcard variable) is less than 32 characters match /{imageId} { allow write: if request.resource.size < 5 * 1024 * 1024 && request.resource.contentType.matches('image/.*') && request.resource.contentType == resource.contentType && imageId.size() < 32 } } } }
Realtime Database
Struttura di base
In Realtime Database, Firebase Security Rules è costituito da espressioni simili a JavaScript contenute in un documento JSON.
Utilizzano la seguente sintassi:
{
"rules": {
"<<path>>": {
// Allow the request if the condition for each method is true.
".read": <<condition>>,
".write": <<condition>>,
".validate": <<condition>>
}
}
}
La regola contiene tre elementi di base:
- Percorso: la posizione del database. Questo rispecchia la struttura JSON del database.
- Richiesta:sono i metodi utilizzati dalla regola per concedere l'accesso. Le regole
read
ewrite
concedono un accesso ampio in lettura e scrittura, mentre le regolevalidate
agiscono come verifica secondaria per concedere l'accesso in base ai dati in entrata o esistenti. - Condizione:la condizione che consente una richiesta se ha valore true.
Come vengono applicate le regole ai percorsi
In Realtime Database, Rules si applicano in modo atomico, il che significa che le regole dei nodi principali di livello superiore sostituiscono le regole dei nodi secondari più granulari e le regole di un nodo più profondo non possono concedere l'accesso a un percorso principale. Non puoi perfezionare o revocare l'accesso a un percorso più profondo nella struttura del database se lo hai già concesso per uno dei percorsi principali.
Considera le seguenti regole:
{ "rules": { "foo": { // allows read to /foo/* ".read": "data.child('baz').val() === true", "bar": { // ignored, since read was allowed already ".read": false } } } }
Questa struttura di sicurezza consente di leggere /bar/
ogni volta che
/foo/
contiene un elemento figlio baz
con valore true
.
La regola ".read": false
in /foo/bar/
non ha alcun effetto in questo caso, poiché l'accesso non può essere revocato da un percorso secondario.
Sebbene possa non sembrare immediatamente intuitivo, si tratta di una parte efficace del linguaggio delle regole e consente di implementare privilegi di accesso molto complessi con il minimo sforzo. Questo è particolarmente utile per la sicurezza basata sugli utenti.
Tuttavia, le regole .validate
non si applicano in modo ricorsivo. Affinché una scrittura sia consentita, tutte le regole di convalida devono essere soddisfatte a tutti i livelli della gerarchia.
Inoltre, poiché le regole non vengono applicate nuovamente a un percorso principale, le operazioni di lettura o scrittura non vanno a buon fine se non è presente una regola nella posizione richiesta o in una posizione principale che concede l'accesso. Anche se ogni percorso secondario interessato è accessibile, la lettura nella posizione principale non andrà a buon fine. Considera questa struttura:
{ "rules": { "records": { "rec1": { ".read": true }, "rec2": { ".read": false } } } }
Se non si comprende che le regole vengono valutate in modo atomico, potrebbe sembrare che il recupero del percorso /records/
restituisca rec1
, ma non rec2
. Il risultato effettivo, tuttavia, è un errore:
JavaScript
var db = firebase.database(); db.ref("records").once("value", function(snap) { // success method is not called }, function(err) { // error callback triggered with PERMISSION_DENIED });
Objective-C
FIRDatabaseReference *ref = [[FIRDatabase database] reference]; [[_ref child:@"records"] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) { // success block is not called } withCancelBlock:^(NSError * _Nonnull error) { // cancel block triggered with PERMISSION_DENIED }];
Swift
var ref = FIRDatabase.database().reference() ref.child("records").observeSingleEventOfType(.Value, withBlock: { snapshot in // success block is not called }, withCancelBlock: { error in // cancel block triggered with PERMISSION_DENIED })
Java
FirebaseDatabase database = FirebaseDatabase.getInstance(); DatabaseReference ref = database.getReference("records"); ref.addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot snapshot) { // success method is not called } @Override public void onCancelled(FirebaseError firebaseError) { // error callback triggered with PERMISSION_DENIED }); });
REST
curl https://docs-examples.firebaseio.com/rest/records/ # response returns a PERMISSION_DENIED error
Poiché l'operazione di lettura in /records/
è atomica e non esiste una regola di lettura che consenta l'accesso a tutti i dati in /records/
, verrà generato un errore PERMISSION_DENIED
. Se valutiamo questa regola nel simulatore di sicurezza nella nostra console Firebase, possiamo vedere che l'operazione di lettura è stata rifiutata:
Attempt to read /records with auth=Success(null) / /records No .read rule allowed the operation. Read was denied.
L'operazione è stata rifiutata perché nessuna regola di lettura consentiva l'accesso al percorso /records/
, ma tieni presente che la regola per /records/
non è mai stata valutata perché non era nel percorso che abbiamo richiesto.rec1
Per recuperare rec1
, dobbiamo accedervi direttamente:
JavaScript
var db = firebase.database(); db.ref("records/rec1").once("value", function(snap) { // SUCCESS! }, function(err) { // error callback is not called });
Objective-C
FIRDatabaseReference *ref = [[FIRDatabase database] reference]; [[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) { // SUCCESS! }];
Swift
var ref = FIRDatabase.database().reference() ref.child("records/rec1").observeSingleEventOfType(.Value, withBlock: { snapshot in // SUCCESS! })
Java
FirebaseDatabase database = FirebaseDatabase.getInstance(); DatabaseReference ref = database.getReference("records/rec1"); ref.addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot snapshot) { // SUCCESS! } @Override public void onCancelled(FirebaseError firebaseError) { // error callback is not called } });
REST
curl https://docs-examples.firebaseio.com/rest/records/rec1 # SUCCESS!
Variabile posizione
Realtime Database Rules supportano una variabile $location
per associare i segmenti di percorso. Utilizza il prefisso $
davanti al segmento del percorso per associare la regola a eventuali nodi secondari lungo il percorso.
{
"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')"
}
}
}
}
}
Puoi anche utilizzare $variable
in parallelo con nomi di percorso costanti.
{
"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 }
}
}
}