Dieser Leitfaden baut auf dem Sprachleitfaden Learn the Core Firebase Security Rules auf, um zu zeigen, wie Sie Bedingungen zu Ihren Firebase Realtime Database Security Rules hinzufügen.
Der primäre Baustein der Echtzeit-Datenbanksicherheitsregeln ist die Bedingung . Eine Bedingung ist ein boolescher Ausdruck, der festlegt, ob eine bestimmte Operation zugelassen oder verweigert werden soll. Für grundlegende Regeln funktioniert die Verwendung von true
und false
Literalen als Bedingungen hervorragend. Aber die Sprache der Echtzeit-Datenbanksicherheitsregeln gibt Ihnen Möglichkeiten, komplexere Bedingungen zu schreiben, die Folgendes können:
- Überprüfen Sie die Benutzerauthentifizierung
- Vergleichen Sie vorhandene Daten mit neu eingereichten Daten
- Greifen Sie auf verschiedene Teile Ihrer Datenbank zu und vergleichen Sie sie
- Validieren Sie eingehende Daten
- Verwenden Sie die Struktur eingehender Abfragen für die Sicherheitslogik
Verwenden von $-Variablen zum Erfassen von Pfadsegmenten
Sie können Teile des Pfads für einen Lese- oder Schreibvorgang erfassen, indem Sie Erfassungsvariablen mit dem Präfix $
deklarieren. Dies dient als Platzhalter und speichert den Wert dieses Schlüssels zur Verwendung innerhalb der Regelbedingungen:
{ "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')" } } } } }
Die dynamischen $
-Variablen können auch parallel mit konstanten Pfadnamen verwendet werden. In diesem Beispiel verwenden wir die $other
-Variable, um eine .validate
-Regel zu deklarieren, die sicherstellt, dass widget
außer title
und color
keine untergeordneten Elemente hat. Jeder Schreibvorgang, der dazu führen würde, dass zusätzliche untergeordnete Elemente erstellt würden, würde fehlschlagen.
{ "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 } } } }
Authentifizierung
Eines der häufigsten Sicherheitsregelmuster ist die Steuerung des Zugriffs basierend auf dem Authentifizierungsstatus des Benutzers. Beispielsweise möchte Ihre App möglicherweise nur angemeldeten Benutzern das Schreiben von Daten erlauben.
Wenn Ihre App die Firebase-Authentifizierung verwendet, enthält die Variable request.auth
die Authentifizierungsinformationen für den Client, der Daten anfordert. Weitere Informationen zu request.auth
finden Sie in der Referenzdokumentation .
Die Firebase-Authentifizierung lässt sich in die Firebase-Echtzeitdatenbank integrieren, damit Sie den Datenzugriff auf Benutzerbasis mithilfe von Bedingungen steuern können. Sobald sich ein Benutzer authentifiziert, wird die Variable auth
in Ihren Regeln für die Echtzeit-Datenbanksicherheit mit den Informationen des Benutzers gefüllt. Diese Informationen umfassen ihre eindeutige Kennung ( uid
) sowie verknüpfte Kontodaten, wie eine Facebook-ID oder eine E-Mail-Adresse, und andere Informationen. Wenn Sie einen benutzerdefinierten Authentifizierungsanbieter implementieren, können Sie Ihre eigenen Felder zur Authentifizierungsnutzlast Ihres Benutzers hinzufügen.
In diesem Abschnitt wird erläutert, wie Sie die Sprache der Sicherheitsregeln für die Firebase-Echtzeitdatenbank mit Authentifizierungsinformationen zu Ihren Benutzern kombinieren. Durch die Kombination dieser beiden Konzepte können Sie den Zugriff auf Daten basierend auf der Benutzeridentität steuern.
Die auth
-Variable
Die vordefinierte auth
in den Regeln ist null, bevor die Authentifizierung stattfindet.
Sobald ein Benutzer mit der Firebase-Authentifizierung authentifiziert ist, enthält er die folgenden Attribute:
Anbieter | Die verwendete Authentifizierungsmethode („Passwort“, „anonym“, „facebook“, „github“, „google“ oder „twitter“). |
Flüssigkeit | Eine eindeutige Benutzer-ID, die bei allen Anbietern garantiert eindeutig ist. |
Zeichen | Der Inhalt des Firebase-Authentifizierungs-ID-Tokens. Weitere Einzelheiten finden Sie in der Referenzdokumentation für auth.token . |
Hier ist eine Beispielregel, die die Variable auth
verwendet, um sicherzustellen, dass jeder Benutzer nur in einen benutzerspezifischen Pfad schreiben kann:
{ "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" } } } }
Strukturieren Ihrer Datenbank zur Unterstützung von Authentifizierungsbedingungen
Normalerweise ist es hilfreich, Ihre Datenbank so zu strukturieren, dass das Schreiben von Regeln einfacher wird. Ein gängiges Muster zum Speichern von Benutzerdaten in der Echtzeitdatenbank besteht darin, alle Ihre Benutzer in einem einzigen users
zu speichern, dessen Kinder die uid
Werte für jeden Benutzer sind. Wenn Sie den Zugriff auf diese Daten einschränken möchten, sodass nur der angemeldete Benutzer seine eigenen Daten sehen kann, würden Ihre Regeln in etwa so aussehen.
{ "rules": { "users": { "$uid": { ".read": "auth !== null && auth.uid === $uid" } } } }
Arbeiten mit benutzerdefinierten Authentifizierungsansprüchen
Für Apps, die eine benutzerdefinierte Zugriffssteuerung für verschiedene Benutzer erfordern, ermöglicht die Firebase-Authentifizierung Entwicklern , Ansprüche auf einen Firebase-Benutzer festzulegen . Auf diese Ansprüche kann in Ihren Regeln in der Variablen auth.token
zugegriffen werden. Hier ist ein Beispiel für Regeln, die den benutzerdefinierten Anspruch hasEmergencyTowel
verwenden:
{ "rules": { "frood": { // A towel is about the most massively useful thing an interstellar // hitchhiker can have ".read": "auth.token.hasEmergencyTowel === true" } } }
Entwickler, die ihre eigenen benutzerdefinierten Authentifizierungstoken erstellen, können diesen Token optional Ansprüche hinzufügen. Diese Ansprüche sind für die Variable auth.token
in Ihren Regeln verfügbar.
Vorhandene Daten vs. neue Daten
Die vordefinierte data
wird verwendet, um auf die Daten zu verweisen, bevor eine Schreiboperation stattfindet. Umgekehrt enthält die Variable newData
die neuen Daten, die vorhanden sein werden, wenn der Schreibvorgang erfolgreich ist. newData
stellt das zusammengeführte Ergebnis der neu geschriebenen Daten und der vorhandenen Daten dar.
Zur Veranschaulichung würde uns diese Regel erlauben, neue Datensätze zu erstellen oder vorhandene zu löschen, aber keine Änderungen an vorhandenen Nicht-Null-Daten vorzunehmen:
// 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()"
Verweisen auf Daten in anderen Pfaden
Als Kriterium für Regeln können beliebige Daten verwendet werden. Mit den vordefinierten Variablen root
, data
und newData
können wir auf jeden Pfad zugreifen, wie er vor oder nach einem Schreibereignis vorhanden wäre.
Betrachten Sie dieses Beispiel, das Schreibvorgänge zulässt, solange der Wert des Knotens /allow_writes/
true
ist, der übergeordnete Knoten kein readOnly
Flag gesetzt hat und es in den neu geschriebenen Daten ein untergeordnetes Element namens foo
gibt:
".write": "root.child('allow_writes').val() === true && !data.parent().child('readOnly').exists() && newData.child('foo').exists()"
Validierung von Daten
Das Erzwingen von Datenstrukturen und das Validieren des Formats und Inhalts von Daten sollte mithilfe von .validate
Regeln erfolgen, die erst ausgeführt werden, nachdem eine .write
Regel erfolgreich den Zugriff gewährt hat. Nachfolgend finden Sie ein Beispiel für eine .validate
-Regeldefinition, die nur Datumsangaben im Format JJJJ-MM-TT zwischen den Jahren 1900-2099 zulässt, die mit einem regulären Ausdruck überprüft werden.
".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])$/)"
Die .validate
Regeln sind der einzige Sicherheitsregeltyp, der nicht kaskadiert wird. Wenn eine Validierungsregel für einen untergeordneten Datensatz fehlschlägt, wird der gesamte Schreibvorgang abgelehnt. Darüber hinaus werden die Validate-Definitionen ignoriert, wenn Daten gelöscht werden (d. h. wenn der neu geschriebene Wert null
ist).
Diese Punkte mögen trivial erscheinen, sind aber tatsächlich wichtige Funktionen für das Schreiben leistungsstarker Sicherheitsregeln für Firebase-Echtzeitdatenbanken. Beachten Sie die folgenden Regeln:
{ "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()" } } } }
Sehen Sie sich mit dieser Variante die Ergebnisse für die folgenden Schreibvorgänge an:
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);
Ziel 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];
Schnell
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);
AUSRUHEN
# 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
Schauen wir uns nun dieselbe Struktur an, verwenden jedoch .write
Regeln anstelle von .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 dieser Variante wäre jede der folgenden Operationen erfolgreich:
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);
Ziel 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];
Schnell
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);
AUSRUHEN
# 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
Dies veranschaulicht die Unterschiede zwischen .write
und .validate
Regeln. Wie demonstriert, sollten alle diese Regeln mit .validate
geschrieben werden, mit der möglichen Ausnahme der Regel newData.hasChildren()
, die davon abhängen würde, ob Löschungen zulässig sein sollen.
Abfragebasierte Regeln
Obwohl Sie Regeln nicht als Filter verwenden können , können Sie den Zugriff auf Teilmengen von Daten einschränken, indem Sie Abfrageparameter in Ihren Regeln verwenden. query.
Ausdrücke in Ihren Regeln, um basierend auf Abfrageparametern Lese- oder Schreibzugriff zu gewähren.
Beispielsweise verwendet die folgende abfragebasierte Regel benutzerbasierte Sicherheitsregeln und abfragebasierte Regeln, um den Zugriff auf Daten in der baskets
Sammlung nur auf die Warenkörbe zu beschränken, die der aktive Benutzer besitzt:
"baskets": {
".read": "auth.uid !== null &&
query.orderByChild === 'owner' &&
query.equalTo === auth.uid" // restrict basket access to owner of basket
}
Die folgende Abfrage, die die Abfrageparameter in der Regel enthält, wäre erfolgreich:
db.ref("baskets").orderByChild("owner")
.equalTo(auth.currentUser.uid)
.on("value", cb) // Would succeed
Abfragen, die die Parameter in der Regel nicht enthalten, würden jedoch mit einem PermissionDenied
-Fehler fehlschlagen:
db.ref("baskets").on("value", cb) // Would fail with PermissionDenied
Sie können auch abfragebasierte Regeln verwenden, um zu begrenzen, wie viele Daten ein Client durch Lesevorgänge herunterlädt.
Beispielsweise beschränkt die folgende Regel den Lesezugriff auf nur die ersten 1000 Ergebnisse einer Abfrage, sortiert nach Priorität:
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)
Die folgende query.
Ausdrücke sind in Echtzeit-Datenbanksicherheitsregeln verfügbar.
Abfragebasierte Regelausdrücke | ||
---|---|---|
Ausdruck | Typ | Beschreibung |
query.orderByKey query.orderByPriority query.orderByValue | boolesch | True für nach Schlüssel, Priorität oder Wert geordnete Abfragen. Sonst falsch. |
query.orderByChild | Schnur Null | Verwenden Sie eine Zeichenfolge, um den relativen Pfad zu einem untergeordneten Knoten darzustellen. Beispiel: query.orderByChild === "address/zip" . Wenn die Abfrage nicht nach einem untergeordneten Knoten geordnet ist, ist dieser Wert null. |
query.startAt query.endAt query.equalTo | Schnur Nummer boolesch Null | Ruft die Grenzen der ausgeführten Abfrage ab oder gibt null zurück, wenn kein gebundener Satz vorhanden ist. |
query.limitToFirst query.limitToLast | Nummer Null | Ruft das Limit für die ausgeführte Abfrage ab oder gibt null zurück, wenn kein Limit festgelegt ist. |
Nächste Schritte
Nach dieser Erörterung der Bedingungen haben Sie ein differenzierteres Verständnis der Regeln und sind bereit:
Erfahren Sie, wie Sie mit Kernanwendungsfällen umgehen, und lernen Sie den Arbeitsablauf zum Entwickeln, Testen und Bereitstellen von Regeln kennen:
- Erfahren Sie mehr über den vollständigen Satz vordefinierter Rules- Variablen, die Sie zum Erstellen von Bedingungen verwenden können .
- Schreiben Sie Regeln für gängige Szenarien .
- Bauen Sie auf Ihrem Wissen auf, indem Sie Situationen überprüfen, in denen Sie unsichere Regeln erkennen und vermeiden müssen.
- Erfahren Sie mehr über die Firebase Local Emulator Suite und wie Sie sie zum Testen von Regeln verwenden können .
- Sehen Sie sich die verfügbaren Methoden zum Bereitstellen von Regeln an.
Learn Rules-Funktionen, die für Realtime Database spezifisch sind:
- Erfahren Sie, wie Sie Ihre Echtzeitdatenbank indizieren .
- Überprüfen Sie die REST-API zum Bereitstellen von Regeln .