Die grundlegende Syntax der Realtime Database-Sicherheitsregeln

Mit Firebase Realtime Database-Sicherheitsregeln können Sie den Zugriff auf in Ihrer Datenbank gespeicherte Daten steuern. Mit der flexiblen Regelsyntax können Sie Regeln für beliebige Bereiche erstellen – von sämtlichen Schreibvorgängen über die gesamte Datenbank bis hin zu Vorgängen in einzelnen Knoten.

Realtime Database-Sicherheitsregeln sind deklarative Konfiguration für Ihre Datenbank. Das bedeutet, dass die Regeln unabhängig von der Produktlogik definiert werden. Das hat eine Reihe von Vorteilen: Clients sind nicht für die Durchsetzung der Sicherheit verantwortlich, fehlerhafte Implementierungen gefährden Ihre Daten nicht und was vielleicht am wichtigsten ist: Es ist kein Zwischenschiedsrichter wie ein Server erforderlich, um Daten vor der Welt zu schützen.

In diesem Thema werden die grundlegende Syntax und die Struktur von Realtime Database-Sicherheitsregeln beschrieben, mit denen vollständige Regelsätze erstellt werden.

Sicherheitsregeln strukturieren

Realtime Database-Sicherheitsregeln bestehen aus JavaScript-ähnlichen Ausdrücken in einem JSON-Dokument. Die Struktur Ihrer Regeln sollte der Struktur der Daten entsprechen, die Sie in Ihrer Datenbank gespeichert haben.

Mithilfe von Basisregeln werden eine Reihe von Knoten identifiziert, die gesichert werden sollen, die beteiligten Zugriffsmethoden (z.B. Lesen, Schreiben) und die Bedingungen, unter denen der Zugriff entweder zugelassen oder abgelehnt wird. In den folgenden Beispielen sind unsere Bedingungen einfache true- und false-Anweisungen. Im nächsten Thema werden wir jedoch dynamischere Möglichkeiten zur Ausdrucksweise von Bedingungen behandeln.

Wenn wir beispielsweise versuchen, eine child_node unter einer parent_node zu sichern, lautet die allgemeine Syntax:

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

Wenden wir dieses Muster an. Angenommen, Sie erfassen eine Liste von Nachrichten und haben folgende Daten:

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

Ihre Regeln sollten in ähnlicher Weise strukturiert sein. Hier sind einige Regeln für die schreibgeschützte Sicherheit, die für diese Datenstruktur sinnvoll sein könnten. In diesem Beispiel wird veranschaulicht, wie Datenbankknoten angegeben werden, auf die Regeln angewendet werden, und wie die Bedingungen für die Auswertung von Regeln an diesen Knoten festgelegt werden.

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

Grundlegende Regeln für Vorgänge

Es gibt drei Arten von Regeln zur Durchsetzung der Sicherheit, die auf der Art der Operation basieren, die auf den Daten ausgeführt wird: .write, .read und .validate. Hier eine kurze Zusammenfassung der Zwecke:

Regeltypen
.read Beschreibt, ob und wann Daten von Nutzern gelesen werden dürfen.
.write Hier wird beschrieben, ob und wann Daten geschrieben werden dürfen.
.validate Hier wird definiert, wie ein korrekt formatierter Wert aussehen soll, ob es untergeordnete Attribute gibt und der Datentyp.

Platzhaltervariablen für die Erfassung

Alle Regeln verweisen auf Knoten. Eine Anweisung kann auf einen bestimmten Knoten verweisen oder $-Platzhalter Capture-Variablen verwenden, um auf Knotengruppen auf einer Ebene der Hierarchie zu verweisen. Mit diesen Erfassungsvariablen können Sie den Wert von Knotenschlüsseln zur Verwendung in nachfolgenden Regelausdrücken speichern. Mit dieser Methode können Sie komplexere Rules Bedingungen schreiben. Das wird im nächsten Thema ausführlicher behandelt.

{
  "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 wird die Variable $other verwendet, um eine .validate-Regel zu deklarieren, die dafür sorgt, dass widget keine anderen untergeordneten Elemente als title und color hat. Jeder Schreibvorgang, der zur Erstellung zusätzlicher untergeordneter Elemente führen würde, 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 }
    }
  }
}

Kaskade von Lese- und Schreibregeln

.read- und .write-Regeln werden von oben nach unten angewendet. Dabei werden Regeln mit niedrigerer Priorität von Regeln mit höherer Priorität überschrieben. Wenn eine Regel Lese- oder Schreibberechtigungen für einen bestimmten Pfad gewährt, wird auch Zugriff auf alle untergeordneten Knoten gewährt. Betrachten Sie die folgende Struktur:

{
  "rules": {
     "foo": {
        // allows read to /foo/*
        ".read": "data.child('baz').val() === true",
        "bar": {
          /* ignored, since read was allowed already */
          ".read": false
        }
     }
  }
}

Mit dieser Sicherheitsstruktur kann /bar/ gelesen werden, wenn /foo/ ein untergeordnetes Element baz mit dem Wert true enthält. Die Regel ".read": false unter /foo/bar/ hat hier keine Auswirkungen, da der Zugriff nicht über einen untergeordneten Pfad widerrufen werden kann.

Dieser Teil der Regelsprache ist zwar nicht sofort intuitiv, bietet aber sehr umfangreiche Möglichkeiten und ermöglicht die Implementierung sehr komplexer Zugriffsberechtigungen mit minimalem Aufwand. Das wird später in diesem Leitfaden anhand der nutzerbasierten Sicherheit veranschaulicht.

.validate-Regeln werden nicht kaskadiert. Alle Validierungsregeln müssen auf allen Ebenen der Hierarchie erfüllt sein, damit ein Schreibvorgang zulässig ist.

Regeln sind keine Filter

Regeln werden in kleinstmöglichen Schritten angewendet. Das bedeutet, dass ein Lese- oder Schreibvorgang sofort fehlschlägt, wenn es an diesem Speicherort oder an einem übergeordneten Standort keine Regel gibt, die Zugriff gewährt. Selbst wenn auf jeden betroffenen untergeordneten Pfad zugegriffen werden kann, schlägt das Lesen am übergeordneten Speicherort vollständig fehl. Betrachten Sie diese Struktur:

{
  "rules": {
    "records": {
      "rec1": {
        ".read": true
      },
      "rec2": {
        ".read": false
      }
    }
  }
}

Wenn Sie nicht wissen, dass Regeln atomar ausgewertet werden, könnte es so aussehen, als würde beim Abrufen des Pfads /records/ rec1 zurückgegeben, aber nicht rec2. Das tatsächliche Ergebnis ist jedoch ein Fehler:

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
Hinweis: Dieses Firebase-Produkt ist nicht im App Clip-Ziel verfügbar.
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
Hinweis: Dieses Firebase-Produkt ist für das App-Clip-Ziel nicht verfügbar.
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

Da der Lesevorgang bei /records/ atomar ist und es keine Leseregel gibt, die Zugriff auf alle Daten unter /records/ gewährt, wird ein PERMISSION_DENIED-Fehler ausgegeben. Wenn wir diese Regel im Sicherheitssimulator in der Firebase-Konsole auswerten, sehen wir, dass der Lesevorgang abgelehnt wurde, weil keine Leseregel den Zugriff auf den Pfad /records/ erlaubt hat. Die Regel für rec1 wurde jedoch nie ausgewertet, da sie nicht im angeforderten Pfad enthalten war. Um rec1 abzurufen, müssten wir direkt darauf zugreifen:

JavaScript
var db = firebase.database();
db.ref("records/rec1").once("value", function(snap) {
  // SUCCESS!
}, function(err) {
  // error callback is not called
});
Objective-C
Hinweis: Dieses Firebase-Produkt ist nicht im App Clip-Ziel verfügbar.
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
[[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
    // SUCCESS!
}];
Swift
Hinweis: Dieses Firebase-Produkt ist für das App-Clip-Ziel nicht verfügbar.
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!

Überlappende Anweisungen

Es ist möglich, dass auf einen Knoten mehrere Regeln angewendet werden. Wenn ein Knoten durch mehrere Regelausdrücke identifiziert wird, wird die Zugriffsmethode abgelehnt, wenn eine der Bedingungen false ist:

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

Im obigen Beispiel werden Lesezugriffe auf den Knoten message1 abgelehnt, weil die zweite Regel immer false ist, obwohl die erste Regel immer true ist.

Nächste Schritte

Weitere Informationen zu den Sicherheitsregeln der Firebase Realtime Database:

  • Das nächste wichtige Konzept der Rules-Sprache sind dynamische Bedingungen. Mit diesen können Sie unter anderem die Nutzerautorisierung prüfen, vorhandene und eingehende Daten vergleichen, eingehende Daten validieren und die Struktur von Abfragen vom Client prüfen.Rules

  • Sehen Sie sich typische Anwendungsfälle für die Sicherheit und die Definitionen der Firebase-Sicherheitsregeln an, die sich darauf beziehen.