Bedingungen in Realtime Database-Sicherheitsregeln verwenden

Dieser Leitfaden baut auf dem Leitfaden Grundlagen der Firebase-Sicherheitsregelsprache auf. Sie erfahren hier, wie Sie Ihren Firebase Realtime Database-Sicherheitsregeln Bedingungen hinzufügen.

Der primäre Baustein der Sicherheitsregeln für die Realtime Database ist die Bedingung. Eine Bedingung ist ein boolescher Ausdruck, der festlegt, ob ein bestimmter Vorgang zulässig oder nicht zulässig ist. Für einfache Regeln können Sie true- und false-Literale als Bedingungen verwenden. Mit der Sprache für Sicherheitsregeln für die Realtime Database können Sie jedoch komplexere Bedingungen schreiben, die Folgendes ermöglichen:

  • Nutzerauthentifizierung prüfen
  • Vorhandene Daten mit neu eingereichten Daten vergleichen
  • Auf verschiedene Teile Ihrer Datenbank zugreifen und sie vergleichen
  • Eingehende Daten validieren
  • Struktur eingehender Anfragen für Sicherheitslogik verwenden

$ Variablen zum Erfassen von Pfadsegmenten verwenden

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 in den Bedingungen von Regeln:

{
  "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 zu konstanten Pfadnamen verwendet werden. In diesem Beispiel wird mit der Variablen $other eine .validate-Regel deklariert, die dafür sorgt, dass widget keine anderen untergeordneten Elemente als title und color hat. Jeder Schreibvorgang, der zum Erstellen zusätzlicher untergeordneter Elemente führen würde, schlägt fehl.

{
  "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 am häufigsten verwendeten Sicherheitsregelmuster steuert den Zugriff auf Basis des Authentifizierungsstatus des Nutzers. So kann beispielsweise ermöglicht werden, dass nur angemeldete Nutzer Daten schreiben dürfen.

Wenn Ihre Anwendung Firebase Authentication 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.

Firebase Authentication ist in Firebase Realtime Database integriert, sodass Sie den Datenzugriff mithilfe von Bedingungen auf Nutzerbasis steuern können. Sobald sich ein Nutzer authentifiziert hat, wird die Variable auth in Ihren Realtime Database-Sicherheitsregeln mit den Informationen des Nutzers gefüllt. Dazu gehören die 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 der Authentifizierungs-Payload des Nutzers eigene Felder hinzufügen.

In diesem Abschnitt wird erläutert, wie Sie die Sprache der Firebase Realtime Database-Sicherheitsregeln mit Authentifizierungsinformationen zu Ihren Nutzern kombinieren. Durch die Kombination dieser beiden Konzepte können Sie den Zugriff auf Daten basierend auf der Nutzeridentität steuern.

Die Variable auth

Die vordefinierte Variable auth in den Regeln ist vor der Authentifizierung null.

Wenn ein Nutzer mit Firebase Authentication authentifiziert wurde, enthält er die folgenden Attribute:

Anbieter Die verwendete Authentifizierungsmethode („password“, „anonymous“, „facebook“, „github“, „google“ oder „twitter“).
uid Eine eindeutige Nutzer-ID, die garantiert für alle Anbieter eindeutig ist.
Token Der Inhalt des Firebase Auth-ID-Tokens. Weitere Informationen finden Sie in der Referenzdokumentation zu auth.token.

Hier ist ein Beispiel für eine Regel, in der die Variable auth verwendet wird, um sicherzustellen, dass jeder Nutzer nur in einen nutzerspezifischen 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"
      }
    }
  }
}

Datenbank zur Unterstützung von Authentifizierungsbedingungen strukturieren

Es ist in der Regel hilfreich, die Datenbank so zu strukturieren, dass das Schreiben von Rules einfacher ist. Ein gängiges Muster zum Speichern von Nutzerdaten in der Realtime Database besteht darin, alle Nutzer in einem einzelnen users-Knoten zu speichern, dessen untergeordnete Elemente die uid-Werte für jeden Nutzer sind. Wenn Sie den Zugriff auf diese Daten so einschränken möchten, dass nur der angemeldete Nutzer seine eigenen Daten sehen kann, sehen Ihre Regeln in etwa so aus.

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

Mit benutzerdefinierten Authentifizierungsansprüchen arbeiten

Für Apps, die eine benutzerdefinierte Zugriffssteuerung für verschiedene Nutzer erfordern, können Entwickler mit Firebase Authentication Anforderungen für einen Firebase-Nutzer festlegen. Diese Ansprüche sind in Ihren Regeln über die Variableauth.token verfügbar. Hier ist ein Beispiel für Regeln, in denen der benutzerdefinierte Anspruch hasEmergencyTowel verwendet wird:

{
  "rules": {
    "frood": {
      // A towel is about the most massively useful thing an interstellar
      // hitchhiker can have
      ".read": "auth.token.hasEmergencyTowel === true"
    }
  }
}

Entwickler, die eigene benutzerdefinierte Authentifizierungstokens erstellen, können diesen Tokens optional Anforderungen hinzufügen. Diese Ansprüche sind in der Variablen auth.token in Ihren Regeln verfügbar.

Vorhandene Daten im Vergleich zu neuen Daten

Mit der vordefinierten Variablen data wird auf die Daten vor einem Schreibvorgang verwiesen. Die Variable newData enthält die neuen Daten, die vorhanden sind, wenn der Schreibvorgang erfolgreich ist. newData ist das zusammengeführte Ergebnis der neuen Daten, die geschrieben werden, und der vorhandenen Daten.

Mit dieser Regel könnten wir beispielsweise neue Datensätze erstellen oder vorhandene löschen, aber keine Änderungen an vorhandenen Daten vornehmen, die nicht null sind:

// 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()"

Auf Daten in anderen Pfaden verweisen

Alle Daten können als Kriterium für Regeln verwendet werden. Mit den vordefinierten Variablen root, data und newData können wir auf jeden Pfad zugreifen, wie er vor oder nach einem Schreibvorgang vorhanden wäre.

In diesem Beispiel sind Schreibvorgänge zulässig, solange der Wert des Knotens /allow_writes/ true ist, für den übergeordneten Knoten kein readOnly-Flag festgelegt ist und in den neu geschriebenen Daten ein untergeordnetes Element namens foo vorhanden ist:

".write": "root.child('allow_writes').val() === true &&
          !data.parent().child('readOnly').exists() &&
          newData.child('foo').exists()"

Daten validieren

Die Durchsetzung von Datenstrukturen und die Validierung des Formats und Inhalts von Daten sollten mit .validate-Regeln erfolgen, die erst ausgeführt werden, nachdem eine .write-Regel erfolgreich war, um den Zugriff zu gewähren. Unten sehen Sie ein Beispiel für eine .validate-Regeldefinition, die nur Datumsangaben im Format JJJJ-MM-TT zwischen den Jahren 1900 und 2099 zulässt. Dies wird mithilfe eines regulären Ausdrucks geprüft.

".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 die einzigen Sicherheitsregeln, die nicht kaskadieren. Wenn eine Validierungsregel für einen untergeordneten Datensatz fehlschlägt, wird der gesamte Schreibvorgang abgelehnt. Außerdem werden die Validierungsdefinitionen ignoriert, wenn Daten gelöscht werden (d. h., wenn der neue Wert, der geschrieben wird, null ist).

Diese Punkte mögen trivial erscheinen, sind aber tatsächlich wichtige Funktionen zum Schreiben leistungsstarker Sicherheitsregeln für die Firebase Realtime Database. 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 vor diesem Hintergrund 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);
Objective-C
Hinweis:Dieses Firebase-Produkt ist nicht für das App Clip-Ziel verfügbar.
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
Hinweis:Dieses Firebase-Produkt ist nicht für das App Clip-Ziel verfügbar.
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);
REST
# 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

Sehen wir uns nun dieselbe Struktur an, aber mit .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ären alle folgenden Vorgänge 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);
Objective-C
Hinweis:Dieses Firebase-Produkt ist nicht für das App Clip-Ziel verfügbar.
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
Hinweis:Dieses Firebase-Produkt ist nicht für das App Clip-Ziel verfügbar.
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);
REST
# 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

So werden die Unterschiede zwischen .write- und .validate-Regeln veranschaulicht. Wie gezeigt, sollten alle diese Regeln mit .validate geschrieben werden, mit der möglichen Ausnahme der Regel newData.hasChildren(), die davon abhängt, ob Löschungen zulässig sein sollen.

Auf Suchanfragen basierende 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. Verwenden Sie query.-Ausdrücke in Ihren Regeln, um Lese- oder Schreibzugriff basierend auf Abfrageparametern zu gewähren.

In der folgenden abfragebasierten Regel werden beispielsweise nutzerbasierte Sicherheitsregeln und abfragebasierte Regeln verwendet, um den Zugriff auf Daten in der Sammlung baskets auf die Einkaufswagen zu beschränken, die dem aktiven Nutzer gehören:

"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ürde erfolgreich ausgeführt:

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 dem Fehler PermissionDenied 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 über Lesevorgänge herunterlädt.

Die folgende Regel beschränkt beispielsweise den Lesezugriff auf die ersten 1.000 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 folgenden query.-Ausdrücke sind in Realtime Database-Sicherheitsregeln verfügbar.

Auf Suchanfragen basierende Regelausdrücke
Expression Typ Beschreibung
query.orderByKey
query.orderByPriority
query.orderByValue
boolean „True“ für Abfragen, die nach Schlüssel, Priorität oder Wert sortiert sind. Sonst „false“.
query.orderByChild String
null
Verwenden Sie einen String, um den relativen Pfad zu einem untergeordneten Knoten darzustellen. Beispiel: query.orderByChild === "address/zip". Wenn die Abfrage nicht nach einem untergeordneten Knoten sortiert ist, ist dieser Wert null.
query.startAt
query.endAt
query.equalTo
string
number
boolean
null
Ruft die Grenzen der auszuführenden Abfrage ab oder gibt „null“ zurück, wenn keine Grenze festgelegt ist.
query.limitToFirst
query.limitToLast
number
null
Ruft das Limit für die auszuführende Abfrage ab oder gibt „null“ zurück, wenn kein Limit festgelegt ist.

Nächste Schritte

Nach dieser Diskussion der Bedingungen haben Sie ein besseres Verständnis von Rules und können:

Informationen zum Umgang mit wichtigen Anwendungsfällen und zum Workflow für die Entwicklung, das Testen und die Bereitstellung von Rules:

Rules-Funktionen, die speziell für Realtime Database entwickelt wurden: