Impara la sintassi di base del linguaggio Realtime Database Security Rules

Le regole di sicurezza del database Firebase Realtime ti consentono di controllare l'accesso ai dati archiviati nel tuo database. La sintassi flessibile delle regole ti consente di creare regole che corrispondono a qualsiasi cosa, da tutte le scritture sul database alle operazioni sui singoli nodi.

Le regole di sicurezza del database in tempo reale sono una configurazione dichiarativa per il database. Ciò significa che le regole sono definite separatamente dalla logica del prodotto. Ciò presenta numerosi vantaggi: i client non sono responsabili dell'applicazione della sicurezza, le implementazioni difettose non comprometteranno i dati e, cosa forse più importante, non è necessario un arbitro intermedio, come un server, per proteggere i dati dal mondo.

Questo argomento descrive la sintassi e la struttura di base delle regole di sicurezza del database in tempo reale utilizzate per creare set di regole completi.

Strutturare le regole di sicurezza

Le regole di sicurezza del database in tempo reale sono costituite da espressioni simili a JavaScript contenute in un documento JSON. La struttura delle tue regole dovrebbe seguire la struttura dei dati che hai archiviato nel tuo database.

Le regole di base identificano un insieme di nodi da proteggere, i metodi di accesso coinvolti (ad esempio, lettura, scrittura) e le condizioni in base alle quali l'accesso è consentito o negato. Negli esempi seguenti, le nostre condizioni saranno semplici affermazioni true e false , ma nell'argomento successivo tratteremo modi più dinamici per esprimere le condizioni.

Quindi, ad esempio, se stiamo cercando di proteggere un child_node sotto un parent_node , la sintassi generale da seguire è:

{
  "rules": {
    "parent_node": {
      "child_node": {
        ".read": <condition>,
        ".write": <condition>,
        ".validate": <condition>,
      }
    }
  }
}

Applichiamo questo modello. Ad esempio, supponiamo che tu stia tenendo traccia di un elenco di messaggi e disponga di dati simili a questi:

{
  "messages": {
    "message0": {
      "content": "Hello",
      "timestamp": 1405704370369
    },
    "message1": {
      "content": "Goodbye",
      "timestamp": 1405704395231
    },
    ...
  }
}

Le tue regole dovrebbero essere strutturate in modo simile. Ecco una serie di regole per la sicurezza di sola lettura che potrebbero avere senso per questa struttura di dati. Questo esempio illustra come specifichiamo i nodi del database a cui si applicano le regole e le condizioni per valutare le regole su tali nodi.

{
  "rules": {
    // For requests to access the 'messages' node...
    "messages": {
      // ...and the individual wildcarded 'message' nodes beneath
      // (we'll cover wildcarding variables more a bit later)....
      "$message": {

        // For each message, allow a read operation if <condition>. In this
        // case, we specify our condition as "true", so read access is always granted.
        ".read": "true",

        // For read-only behavior, we specify that for write operations, our
        // condition is false.
        ".write": "false"
      }
    }
  }
}

Operazioni con regole di base

Esistono tre tipi di regole per applicare la sicurezza in base al tipo di operazione eseguita sui dati: .write , .read e .validate . Ecco un breve riepilogo dei loro scopi:

Tipi di regole
.Leggere Descrive se e quando i dati possono essere letti dagli utenti.
.scrivere Descrive se e quando è consentita la scrittura dei dati.
.convalidare Definisce l'aspetto di un valore formattato correttamente, se ha attributi secondari e il tipo di dati.

Variabili di acquisizione con caratteri jolly

Tutte le istruzioni sulle regole puntano ai nodi. Un'istruzione può puntare a un nodo specifico o utilizzare variabili di acquisizione con caratteri jolly $ per puntare a insiemi di nodi a un livello della gerarchia. Utilizzare queste variabili di acquisizione per archiviare il valore delle chiavi del nodo da utilizzare all'interno delle successive istruzioni di regole. Questa tecnica ti consente di scrivere condizioni di regole più complesse, qualcosa che tratteremo più in dettaglio nel prossimo argomento.

{
  "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 utilizzando la variabile $other per dichiarare una regola .validate che garantisce che widget non abbia figli diversi da title e color . Qualsiasi scrittura che comporterebbe la creazione di ulteriori figli fallirebbe.

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

Leggere e scrivere regole a cascata

Le regole .read e .write funzionano dall'alto verso il basso, con regole meno profonde che prevalgono su quelle più profonde. Se una regola concede autorizzazioni di lettura o scrittura su un percorso particolare, concede anche l'accesso a tutti i nodi figlio sotto di esso. Consideriamo la seguente struttura:

{
  "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ò verrà illustrato quando parleremo della sicurezza basata sull'utente più avanti in questa guida.

Tieni presente che le regole .validate non si sovrappongono. Tutte le regole di convalida devono essere soddisfatte a tutti i livelli della gerarchia affinché una scrittura sia consentita.

Le regole non sono filtri

Le regole vengono applicate in modo atomico. Ciò significa che un'operazione di lettura o scrittura fallisce immediatamente se non esiste una regola in quella posizione 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 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
});
Obiettivo-C
Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
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
Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
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 perché nessuna regola di lettura consentiva l'accesso al percorso /records/ . Tuttavia, 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
Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
[[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
    // SUCCESS!
}];
Veloce
Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
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!

Affermazioni sovrapposte

È possibile che più di una regola venga applicata a un nodo. Nel caso in cui più espressioni di regole identifichino un nodo, il metodo di accesso viene negato se una qualsiasi delle condizioni è false :

{
  "rules": {
    "messages": {
      // A rule expression that applies to all nodes in the 'messages' node
      "$message": {
        ".read": "true",
        ".write": "true"
      },
      // A second rule expression applying specifically to the 'message1` node
      "message1": {
        ".read": "false",
        ".write": "false"
      }
    }
  }
}

Nell'esempio precedente, le letture al nodo message1 verranno negate perché la seconda regola è sempre false , anche se la prima regola è sempre true .

Prossimi passi

Puoi approfondire la tua comprensione delle regole di sicurezza del database Firebase Realtime:

  • Impara il successivo concetto principale del linguaggio delle Regole, le condizioni dinamiche, che consentono alle tue Regole di verificare l'autorizzazione dell'utente, confrontare i dati esistenti e quelli in entrata, convalidare i dati in entrata, controllare la struttura delle query provenienti dal client e altro ancora.

  • Esamina i tipici casi d'uso della sicurezza e le definizioni delle regole di sicurezza Firebase che li affrontano .