Lernen Sie die Kernsyntax der Sprache „Realtime Database Security Rules“ kennen

Mit Firebase Realtime Database Security Rules können Sie den Zugriff auf in Ihrer Datenbank gespeicherte Daten steuern. Mit der flexiblen Regelsyntax können Sie Regeln erstellen, die alles abdecken, von allen Schreibvorgängen in Ihre Datenbank bis hin zu Vorgängen auf einzelnen Knoten.

Echtzeit-Datenbanksicherheitsregeln sind deklarative Konfigurationen für Ihre Datenbank. Das bedeutet, dass die Regeln getrennt von der Produktlogik definiert werden. Dies hat eine Reihe von Vorteilen: Clients sind nicht für die Durchsetzung der Sicherheit verantwortlich, fehlerhafte Implementierungen gefährden Ihre Daten nicht und vielleicht am wichtigsten ist, dass kein zwischengeschalteter Schiedsrichter wie ein Server erforderlich ist, um Daten vor der Welt zu schützen.

In diesem Thema werden die grundlegende Syntax und Struktur von Echtzeitdatenbank-Sicherheitsregeln beschrieben, die zum Erstellen vollständiger Regelsätze verwendet werden.

Strukturieren Sie Ihre Sicherheitsregeln

Echtzeit-Datenbanksicherheitsregeln bestehen aus JavaScript-ähnlichen Ausdrücken, die in einem JSON-Dokument enthalten sind. Die Struktur Ihrer Regeln sollte der Struktur der Daten folgen, die Sie in Ihrer Datenbank gespeichert haben.

Grundregeln identifizieren eine Reihe von Knoten , die gesichert werden sollen, die beteiligten Zugriffsmethoden (z. B. Lesen, Schreiben) und Bedingungen , unter denen der Zugriff entweder erlaubt oder verweigert wird. In den folgenden Beispielen werden unsere Bedingungen einfache true und false Aussagen sein, aber im nächsten Thema werden wir dynamischere Möglichkeiten zum Ausdruck von Bedingungen behandeln.

Wenn wir beispielsweise versuchen, einen child_node unter einem 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 verfolgen eine Liste von Nachrichten und verfügen über Daten, die wie folgt aussehen:

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

Ihre Regeln sollten ähnlich aufgebaut sein. Hier finden Sie eine Reihe von Regeln für die schreibgeschützte Sicherheit, die für diese Datenstruktur sinnvoll sein könnten. Dieses Beispiel veranschaulicht, wie wir Datenbankknoten angeben, für die Regeln gelten, und die Bedingungen für die Auswertung von Regeln an diesen Knoten.

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

Grundregeln Operationen

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

Regeltypen
.lesen Beschreibt, ob und wann Daten von Benutzern gelesen werden dürfen.
.schreiben Beschreibt, ob und wann Daten geschrieben werden dürfen.
.bestätigen Definiert, wie ein korrekt formatierter Wert aussieht, ob er über untergeordnete Attribute verfügt und welchen Datentyp er hat.

Platzhalter-Erfassungsvariablen

Alle Regelanweisungen verweisen auf Knoten. Eine Anweisung kann auf einen bestimmten Knoten verweisen oder mithilfe von $ -Platzhalter -Capture-Variablen auf Gruppen von Knoten auf einer Ebene der Hierarchie verweisen. Verwenden Sie diese Erfassungsvariablen, um den Wert von Knotenschlüsseln zur Verwendung in nachfolgenden Regelanweisungen zu speichern. Mit dieser Technik können Sie komplexere Regelbedingungen schreiben, worauf wir im nächsten Thema ausführlicher eingehen.

{
  "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 verwenden wir die Variable $other , um eine .validate Regel zu deklarieren, die sicherstellt, dass widget außer title und color keine untergeordneten Elemente 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 funktionieren von oben nach unten, wobei flachere Regeln Vorrang vor tieferen Regeln haben. Wenn eine Regel Lese- oder Schreibberechtigungen für einen bestimmten Pfad gewährt, gewährt sie auch Zugriff auf alle untergeordneten Knoten darunter. 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
        }
     }
  }
}

Diese Sicherheitsstruktur ermöglicht das Lesen von /bar/ immer dann, wenn /foo/ ein untergeordnetes baz mit dem Wert true enthält. Die Regel ".read": false unter /foo/bar/ hat hier keine Auswirkung, da der Zugriff nicht durch einen untergeordneten Pfad widerrufen werden kann.

Auch wenn es nicht sofort intuitiv erscheint, ist dies ein leistungsstarker Teil der Regelsprache und ermöglicht die Implementierung sehr komplexer Zugriffsrechte mit minimalem Aufwand. Dies wird veranschaulicht, wenn wir uns später in diesem Handbuch mit der benutzerbasierten Sicherheit befassen.

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

Regeln sind keine Filter

Regeln werden atomar angewendet. Das bedeutet, dass ein Lese- oder Schreibvorgang sofort fehlschlägt, wenn an diesem Standort oder an einem übergeordneten Standort keine Regel vorhanden ist, die den Zugriff gewährt. Selbst wenn auf alle betroffenen untergeordneten Pfade 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
      }
    }
  }
}

Ohne zu verstehen, dass Regeln atomar ausgewertet werden, könnte es so aussehen, als würde das Abrufen des Pfads /records/ rec1 , aber nicht rec2 zurückgeben. 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
});
Ziel c
Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht 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
}];
Schnell
Hinweis: Dieses Firebase-Produkt ist auf dem 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
  });
});
AUSRUHEN
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 unserer Firebase-Konsole auswerten, können wir sehen, dass der Lesevorgang verweigert wurde, da keine Leseregel den Zugriff auf den /records/ -Pfad erlaubte. Beachten Sie jedoch, dass die Regel für rec1 nie ausgewertet wurde, da sie sich nicht im von uns angeforderten Pfad befand. 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
});
Ziel c
Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
[[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
    // SUCCESS!
}];
Schnell
Hinweis: Dieses Firebase-Produkt ist auf dem 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
  }
});
AUSRUHEN
curl https://docs-examples.firebaseio.com/rest/records/rec1
# SUCCESS!

Überlappende Aussagen

Es ist möglich, dass mehr als eine Regel auf einen Knoten angewendet wird. Wenn mehrere Regelausdrücke einen Knoten identifizieren, wird die Zugriffsmethode verweigert, 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 Lesevorgänge für den Knoten message1 verweigert, da die zweite Regel immer false ist, obwohl die erste Regel immer true ist.

Nächste Schritte

Sie können Ihr Verständnis der Firebase-Echtzeitdatenbank-Sicherheitsregeln vertiefen:

  • Lernen Sie das nächste Hauptkonzept der Regelsprache kennen: dynamische Bedingungen , mit denen Ihre Regeln die Benutzerautorisierung überprüfen, vorhandene und eingehende Daten vergleichen, eingehende Daten validieren, die Struktur von Abfragen überprüfen können, die vom Client kommen, und vieles mehr.

  • Sehen Sie sich typische Sicherheitsanwendungsfälle und die Definitionen der Firebase-Sicherheitsregeln an, die diese behandeln .