La sicurezza può essere uno dei pezzi 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 (chi è un utente) e l'autorizzazione (cosa può fare un utente).
Le regole di sicurezza Firebase rimuovono il livello intermedio (server) e ti consentono di specificare autorizzazioni basate sul percorso per i client che si connettono direttamente ai tuoi dati. Utilizza questa guida per ulteriori informazioni su come vengono applicate le regole alle richieste in arrivo.
Seleziona un prodotto per saperne di più sulle sue regole.
Cloud Fire Store
Struttura basilare
Le regole di sicurezza Firebase in Cloud Firestore e Cloud Storage utilizzano la struttura e la sintassi seguenti:
service <<name>> {
// Match the resource path.
match <<path>> {
// Allow the request if the following conditions are true.
allow <<methods>> : if <<condition>>
}
}
È importante comprendere i seguenti concetti chiave durante la creazione delle regole:
- Richiesta: il metodo o i metodi richiamati nell'istruzione
allow
. Questi sono i metodi che stai consentendo di eseguire. I metodi standard sono:get
,list
,create
,update
edelete
. I metodi praticiread
ewrite
consentono un ampio accesso in lettura e scrittura sul database o sul percorso di archiviazione specificato. - Percorso: il database o il percorso di archiviazione, rappresentato come percorso URI.
- Regola: l'istruzione
allow
, che include una condizione che consente una richiesta se risulta vera.
Regole di sicurezza versione 2
Da maggio 2019 è ora disponibile la versione 2 delle regole di sicurezza Firebase. La versione 2 delle regole modifica il comportamento dei caratteri jolly ricorsivi {name=**}
. È necessario utilizzare la versione 2 se si prevede di utilizzare le query del gruppo di raccolta . È necessario aderire alla versione 2 impostando rules_version = '2';
la prima riga nelle regole di sicurezza:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
Percorsi corrispondenti
Tutte le dichiarazioni di corrispondenza dovrebbero puntare a documenti, non a raccolte. Un'istruzione match può puntare a un documento specifico, come in match /cities/SF
o utilizzare caratteri jolly per puntare a qualsiasi documento nel percorso specificato, come in match /cities/{city}
.
Nell'esempio precedente, l'istruzione match utilizza la sintassi del carattere jolly {city}
. Ciò significa che la regola si applica a qualsiasi documento nella raccolta cities
, come /cities/SF
o /cities/NYC
. Quando vengono valutate le espressioni allow
nell'istruzione match, la variabile city
verrà risolta nel nome del documento della città, come SF
o NYC
.
Sottoraccolte corrispondenti
I dati in Cloud Firestore sono organizzati in raccolte di documenti e ogni documento può estendere la gerarchia tramite sottoraccolte. È importante comprendere come le regole di sicurezza interagiscono con i dati gerarchici.
Considera la situazione in cui ogni documento nella raccolta delle cities
contiene una sottoraccolta landmarks
. Le regole di sicurezza si applicano solo al percorso corrispondente, quindi i controlli di accesso definiti nella raccolta cities
non si applicano alla sottoraccolta landmarks
. Scrivi invece regole esplicite per controllare l'accesso alle sottoraccolte:
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 si annidano le istruzioni match
, il percorso dell'istruzione match
interna è sempre relativo al percorso dell'istruzione match
esterna. Le seguenti regole sono quindi 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 desideri che le regole si applichino a una gerarchia arbitrariamente profonda, utilizza la sintassi ricorsiva dei caratteri jolly, {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 si utilizza la sintassi ricorsiva dei caratteri jolly, la variabile dei caratteri jolly conterrà l'intero segmento del percorso corrispondente, anche se il documento si trova in una sottoraccolta profondamente nidificata. Ad esempio, le regole elencate sopra corrisponderebbero a un documento situato in /cities/SF/landmarks/coit_tower
e il valore della variabile document
sarebbe SF/landmarks/coit_tower
.
Si noti, tuttavia, che il comportamento dei caratteri jolly ricorsivi dipende dalla versione delle regole.
Versione 1
Le regole di sicurezza utilizzano la versione 1 per impostazione predefinita. 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 sottoraccolte ma non nella raccolta cities
, mentre match /cities/{document=**}
corrisponde ad entrambi i documenti nella collezione e sottocollezioni cities
.
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=**}
abbina i documenti in qualsiasi sottoraccolta così come i documenti nella raccolta cities
.
Devi aderire alla versione 2 aggiungendo rules_version = '2';
in cima alle 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 ogni istruzione di corrispondenza, ma nella versione 2 puoi inserire questo carattere jolly ovunque nell'istruzione di corrispondenza. Per 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 raccolta , devi utilizzare la versione 2, consulta Protezione delle query sui gruppi di raccolta .
Dichiarazioni 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 operazioni di lettura e scrittura nella raccolta cities
saranno consentite perché la seconda regola è sempre true
, anche se la prima regola è sempre false
.
Limiti delle regole di sicurezza
Mentre lavori con 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 genera un errore di autorizzazione negata. Alcune chiamate di accesso ai documenti potrebbero essere memorizzate nella cache e le chiamate memorizzate nella cache non vengono conteggiate ai fini dei limiti. |
Profondità massima delle istruzioni match nidificate | 10 |
Lunghezza massima del percorso, in segmenti di percorso, consentita all'interno di una serie di istruzioni match nidificate | 100 |
Numero massimo di variabili di acquisizione del percorso consentite all'interno di una serie di istruzioni match nidificate | 20 |
Profondità massima delle chiamate di funzione | 20 |
Numero massimo di argomenti della funzione | 7 |
Numero massimo di associazioni di variabili let per funzione | 10 |
Numero massimo di chiamate di funzioni ricorsive o cicliche | 0 (non consentito) |
Numero massimo di espressioni valutate per richiesta | 1.000 |
Dimensione massima di un set di regole | I set di regole devono rispettare due limiti di dimensione:
|
Archiviazione nel cloud
Struttura basilare
Le regole di sicurezza Firebase in Cloud Firestore e Cloud Storage utilizzano la struttura e la sintassi seguenti:
service <<name>> {
// Match the resource path.
match <<path>> {
// Allow the request if the following conditions are true.
allow <<methods>> : if <<condition>>
}
}
È importante comprendere i seguenti concetti chiave durante la creazione delle regole:
- Richiesta: il metodo o i metodi richiamati nell'istruzione
allow
. Questi sono i metodi che stai consentendo di eseguire. I metodi standard sono:get
,list
,create
,update
edelete
. I metodi praticiread
ewrite
consentono un ampio accesso in lettura e scrittura sul database o sul percorso di archiviazione specificato. - Percorso: il database o il percorso di archiviazione, rappresentato come percorso URI.
- Regola: l'istruzione
allow
, che include una condizione che consente una richiesta se risulta vera.
Percorsi corrispondenti
Le regole di sicurezza di Cloud Storage match
ai percorsi dei file utilizzati per accedere ai file in Cloud Storage. Le regole possono match
a percorsi esatti o percorsi con caratteri jolly e le regole possono anche essere nidificate. Se nessuna regola di corrispondenza consente un metodo di richiesta o la condizione restituisce false
, la richiesta viene negata.
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 modello utilizzando i caratteri jolly. Un carattere jolly è una variabile denominata che rappresenta una singola stringa come profilePhoto.png
o più segmenti di percorso, come images/profilePhoto.png
.
Un carattere jolly viene creato aggiungendo parentesi graffe attorno al nome del carattere jolly, come {string}
. È possibile dichiarare un carattere jolly a segmenti multipli aggiungendo =**
al nome del carattere jolly, come {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 è l' OR
del risultato di tutte le valutazioni delle regole. Cioè, se una qualsiasi regola a cui corrisponde il file restituisce true
, il risultato è true
.
Nelle regole di cui sopra, 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 dall'interno della match
fornendo il nome del file o l'autorizzazione del percorso:
// Another way to restrict the name of a file match /images/{imageId} { allow read: if imageId == "profilePhoto.png"; }
Le regole di sicurezza di Cloud Storage non si sovrappongono e le regole vengono valutate solo quando il percorso della richiesta corrisponde a un percorso con le regole specificate.
Richiedi valutazione
Caricamenti, download, modifiche ai metadati ed eliminazioni vengono valutati utilizzando la request
inviata 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 è una 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 dell'autenticazione Firebase nell'oggetto request.auth
, che verrà spiegato ulteriormente nella sezione Autenticazione della documentazione.
Un elenco completo delle proprietà nell'oggetto request
è disponibile di seguito:
Proprietà | Tipo | Descrizione |
---|---|---|
auth | mappa<stringa, stringa> | Quando un utente ha effettuato l'accesso, fornisce uid , l'ID univoco dell'utente e token , una mappa delle attestazioni JWT di autenticazione Firebase. Altrimenti sarà null . |
params | mappa<stringa, stringa> | Mappa contenente i parametri di query della richiesta. |
path | sentiero | Un path che rappresenta il percorso in cui viene eseguita la richiesta. |
resource | mappa<stringa, stringa> | 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, potresti anche voler valutare i metadati del file che viene caricato, scaricato, modificato o eliminato. Ciò ti consente di creare regole complesse e potenti che fanno cose come consentire solo il caricamento di file con determinati tipi di contenuto o l'eliminazione solo di file più grandi di una certa dimensione.
Le regole di sicurezza Firebase per Cloud Storage forniscono metadati di file nell'oggetto resource
, che contiene coppie chiave/valore dei metadati visualizzati in un oggetto Cloud Storage. Queste proprietà possono essere ispezionate durante le richieste di read
o write
per garantire l'integrità dei dati.
Nelle richieste write
(come caricamenti, aggiornamenti di metadati ed eliminazioni), oltre all'oggetto resource
, che contiene i metadati del file attualmente esistente nel percorso della richiesta, hai anche la possibilità di utilizzare l'oggetto request.resource
, che contiene un sottoinsieme dei metadati del file da scrivere se la scrittura è consentita. È possibile utilizzare questi due valori per garantire l'integrità dei dati o applicare vincoli dell'applicazione come il tipo o la dimensione del file.
Di seguito è disponibile un elenco completo delle proprietà nell'oggetto resource
:
Proprietà | Tipo | Descrizione |
---|---|---|
name | corda | Il nome completo dell'oggetto |
bucket | corda | Il nome del bucket in cui risiede questo oggetto. |
generation | int | La generazione dell'oggetto Google Cloud Storage di questo oggetto. |
metageneration | int | La metagenerazione dell'oggetto Google Cloud Storage di questo oggetto. |
size | int | La dimensione dell'oggetto in byte. |
timeCreated | timestamp | Un timestamp che rappresenta l'ora in cui è stato creato un oggetto. |
updated | timestamp | Un timestamp che rappresenta l'ora dell'ultimo aggiornamento di un oggetto. |
md5Hash | corda | Un hash MD5 dell'oggetto. |
crc32c | corda | Un hash crc32c dell'oggetto. |
etag | corda | L'etag associato a questo oggetto. |
contentDisposition | corda | La disposizione del contenuto associata a questo oggetto. |
contentEncoding | corda | La codifica del contenuto associato a questo oggetto. |
contentLanguage | corda | La lingua del contenuto associata a questo oggetto. |
contentType | corda | Il tipo di contenuto associato a questo oggetto. |
metadata | mappa<stringa, stringa> | Coppie chiave/valore di metadati personalizzati aggiuntivi specificati dallo sviluppatore. |
request.resource
contiene tutti questi elementi ad eccezione di generation
, metageneration
, etag
, timeCreated
e updated
.
Limiti delle regole di sicurezza
Mentre lavori con 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 documento singolo e richieste di interrogazione. Il superamento di questo limite genera un errore di autorizzazione negata. Le chiamate di accesso agli stessi documenti possono essere memorizzate nella cache e le chiamate memorizzate nella cache non vengono conteggiate ai fini dei limiti. |
Esempio completo
Mettendo tutto insieme, puoi creare un esempio completo di regole per una soluzione di archiviazione di 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 } } } }
Banca dati in tempo reale
Struttura basilare
In Realtime Database, le regole di sicurezza Firebase sono costituite da espressioni simili a JavaScript contenute in un documento JSON.
Usano la seguente sintassi:
{
"rules": {
"<<path>>": {
// Allow the request if the condition for each method is true.
".read": <<condition>>,
".write": <<condition>>,
".validate": <<condition>>
}
}
}
Ci sono tre elementi fondamentali nella regola:
- Percorso: il percorso del database. Questo rispecchia la struttura JSON del tuo database.
- Richiesta: questi sono i metodi utilizzati dalla regola per concedere l'accesso. Le regole
read
ewrite
garantiscono un ampio accesso in lettura e scrittura, mentre le regolevalidate
fungono da verifica secondaria per garantire l'accesso in base ai dati in entrata o esistenti. - Condizione: la condizione che consente una richiesta se risulta vera.
Come si applicano le regole ai percorsi
In Realtime Database, le regole si applicano atomicamente, il che significa che le regole sui nodi principali di livello superiore sovrascrivono le regole sui nodi secondari più granulari e le regole su un nodo più profondo non possono garantire 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 baz
figlio con valore true
. La regola ".read": false
sotto /foo/bar/
non ha alcun effetto qui, poiché l'accesso non può essere revocato da un percorso figlio.
Sebbene possa non sembrare immediatamente intuitivo, questa è una parte potente del linguaggio delle regole e consente di implementare privilegi di accesso molto complessi con il minimo sforzo. Ciò è particolarmente utile per la sicurezza basata sull'utente .
Tuttavia, le regole .validate
non si applicano a cascata. Tutte le regole di convalida devono essere soddisfatte a tutti i livelli della gerarchia affinché una scrittura sia consentita.
Inoltre, poiché le regole non si applicano a un percorso padre, l'operazione di lettura o scrittura non riesce se non è presente una regola nella posizione richiesta o in una posizione padre che concede l'accesso. Anche se ogni percorso figlio interessato è accessibile, la lettura nella posizione padre fallirà completamente. Consideriamo questa struttura:
{ "rules": { "records": { "rec1": { ".read": true }, "rec2": { ".read": false } } } }
Senza comprendere che le regole vengono valutate atomicamente, 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 });
Obiettivo-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 }];
Veloce
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 })
Giava
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 }); });
RIPOSO
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 garantisca l'accesso a tutti i dati in /records/
, ciò genererà un errore PERMISSION_DENIED
. Se valutiamo questa regola nel simulatore di sicurezza nella nostra console Firebase , possiamo vedere che l'operazione di lettura è stata negata:
Attempt to read /records with auth=Success(null) / /records No .read rule allowed the operation. Read was denied.
L'operazione è stata negata perché nessuna regola di lettura consentiva l'accesso al percorso /records/
, ma tieni presente che la regola per rec1
non è mai stata valutata perché non era nel percorso richiesto. Per recuperare rec1
, dovremmo accedervi direttamente:
JavaScript
var db = firebase.database(); db.ref("records/rec1").once("value", function(snap) { // SUCCESS! }, function(err) { // error callback is not called });
Obiettivo-C
FIRDatabaseReference *ref = [[FIRDatabase database] reference]; [[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) { // SUCCESS! }];
Veloce
var ref = FIRDatabase.database().reference() ref.child("records/rec1").observeSingleEventOfType(.Value, withBlock: { snapshot in // SUCCESS! })
Giava
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 } });
RIPOSO
curl https://docs-examples.firebaseio.com/rest/records/rec1 # SUCCESS!
Variabile di posizione
Le regole del database in tempo reale supportano una variabile $location
per far corrispondere i segmenti del percorso. Utilizza il prefisso $
davanti al segmento del percorso per abbinare la regola a qualsiasi nodo figlio 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 la $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 }
}
}
}