Reguły zabezpieczeń Bazy danych czasu rzeczywistego Firebase umożliwiają kontrolowanie dostępu do danych przechowywanych w magazynie danych. Elastyczna składnia reguł umożliwia tworzenie reguł, które pasują do wszystkiego – od wszystkich zapisów w bazie danych po operacje na poszczególnych węzłach.
Reguły zabezpieczeń bazy danych czasu rzeczywistego to deklaratywna konfiguracja bazy danych. Oznacza to, że reguły są definiowane niezależnie od logiki usługi. Ma to wiele zalet: klienci nie są odpowiedzialni za egzekwowanie zabezpieczeń, błędne implementacje nie naruszają bezpieczeństwa danych i, co najważniejsze, nie ma potrzeby stosowania pośrednika, takiego jak serwer, do ochrony danych przed światem.
W tym temacie opisano podstawową składnię i strukturę reguł zabezpieczeń bazy danych czasu rzeczywistego używanych do tworzenia kompletnych zestawów reguł.
Struktura reguł zabezpieczeń
Reguły zabezpieczeń Bazy danych czasu rzeczywistego składają się z wyrażeń podobnych do JavaScriptu zawartych w dokumencie JSON. Struktura reguł powinna być zgodna ze strukturą danych przechowywanych w bazie danych.
Reguły podstawowe identyfikują zestaw węzłów, które mają być zabezpieczone, metody dostępu (np. odczyt, zapis) oraz warunki, w których dostęp jest dozwolony lub zabroniony.
W poniższych przykładach nasze warunki będą prostymi instrukcjami true i false, ale w następnym temacie omówimy bardziej dynamiczne sposoby wyrażania warunków.
Jeśli na przykład chcemy zabezpieczyć child_node w parent_node,
ogólna składnia wygląda tak:
{
"rules": {
"parent_node": {
"child_node": {
".read": <condition>,
".write": <condition>,
".validate": <condition>,
}
}
}
}Zastosujmy ten wzorzec. Załóżmy na przykład, że śledzisz listę wiadomości i masz dane, które wyglądają tak:
{
"messages": {
"message0": {
"content": "Hello",
"timestamp": 1405704370369
},
"message1": {
"content": "Goodbye",
"timestamp": 1405704395231
},
...
}
}Twoje reguły powinny mieć podobną strukturę. Oto zestaw reguł zabezpieczeń tylko do odczytu, które mogą być odpowiednie dla tej struktury danych. Ten przykład pokazuje, jak określić węzły bazy danych, do których mają być stosowane reguły, oraz warunki oceny reguł w tych węzłach.
{ "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" } } } }
Operacje na regułach podstawowych
Istnieją 3 rodzaje reguł egzekwowania zabezpieczeń, które zależą od typu operacji wykonywanej na danych: .write, .read i .validate. Oto krótkie podsumowanie ich przeznaczenia:
| Typy reguł | |
|---|---|
| .read | Określa, czy i kiedy użytkownicy mogą odczytywać dane. |
| .write | Określa, czy i kiedy można zapisywać dane. |
| .validate | Określa, jak będzie wyglądać prawidłowo sformatowana wartość, czy ma atrybuty podrzędne i jaki jest typ danych. |
Zmienne przechwytywania symboli wieloznacznych
Wszystkie instrukcje reguł wskazują węzły. Instrukcja może wskazywać konkretny węzeł lub używać $ symbolu wieloznacznego zmienne przechwytywania, aby wskazywać zbiory węzłów na poziomie hierarchii. Użyj tych zmiennych przechwytywania, aby przechowywać wartości kluczy węzłów do użycia w kolejnych instrukcjach reguł. Ta technika pozwala pisać bardziej złożone Security Rules warunki, co omówimy bardziej szczegółowo w następnym temacie.
{ "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')" } } } } }
Zmiennych dynamicznych $ można też używać równolegle ze stałymi nazwami ścieżek. W tym przykładzie używamy zmiennej $other, aby zadeklarować regułę .validate, która zapewnia, że widget nie ma innych elementów podrzędnych niż title i color.
Każdy zapis, który spowodowałby utworzenie dodatkowych elementów podrzędnych, zakończy się niepowodzeniem.
{
"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 }
}
}
}Kaskadowe reguły odczytu i zapisu
Reguły .read i .write działają od góry do dołu, przy czym reguły na płytszym poziomie zastępują reguły na głębszym poziomie. Jeśli reguła przyznaje uprawnienia do odczytu lub zapisu w określonej ścieżce, przyznaje też dostęp do wszystkich węzłów podrzędnych. Rozważmy taką strukturę:
{
"rules": {
"foo": {
// allows read to /foo/*
".read": "data.child('baz').val() === true",
"bar": {
/* ignored, since read was allowed already */
".read": false
}
}
}
}Ta struktura zabezpieczeń umożliwia odczyt z /bar/, gdy /foo/ zawiera element podrzędny baz o wartości true.
Reguła ".read": false w /foo/bar/ nie ma tu zastosowania, ponieważ nie można unieważnić dostępu za pomocą ścieżki podrzędnej.
Choć może się to wydawać mało intuicyjne, jest to bardzo przydatna funkcja języka reguł, która pozwala na wdrażanie bardzo złożonych uprawnień dostępu przy minimalnym wysiłku. Zostanie to zilustrowane w dalszej części tego przewodnika, gdy przejdziemy do zabezpieczeń opartych na użytkownikach.
Pamiętaj, że reguły .validate nie są kaskadowe. Aby zapis był dozwolony, wszystkie reguły sprawdzania poprawności muszą być spełnione na wszystkich poziomach hierarchii.
Reguły to nie filtry
Reguły są stosowane w sposób niepodzielny. Oznacza to, że operacja odczytu lub zapisu natychmiast kończy się niepowodzeniem, jeśli w danej lokalizacji lub w lokalizacji nadrzędnej nie ma reguły przyznającej dostęp. Nawet jeśli wszystkie ścieżki podrzędne, których dotyczy problem, są dostępne, odczyt w lokalizacji nadrzędnej zakończy się niepowodzeniem. Rozważ tę strukturę:
{
"rules": {
"records": {
"rec1": {
".read": true
},
"rec2": {
".read": false
}
}
}
}Jeśli nie wiesz, że reguły są oceniane niepodzielnie, może się wydawać, że pobranie ścieżki /records/ zwróci rec1, ale nie rec2. Rzeczywisty wynik to jednak błąd:
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
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
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
Operacja odczytu w /records/ jest niepodzielna i nie ma reguły odczytu, która przyznaje dostęp do wszystkich danych w /records/, więc spowoduje to błąd PERMISSION_DENIED. Jeśli ocenimy tę regułę w symulatorze zabezpieczeń w konsoli Firebase, zobaczymy, że operacja odczytu została odrzucona, ponieważ żadna reguła odczytu nie zezwalała na dostęp do ścieżki /records/. Pamiętaj jednak, że reguła dla rec1 nie została oceniona, ponieważ nie znajdowała się w żądanej ścieżce. Aby pobrać rec1, musimy uzyskać do niego bezpośredni dostęp:
JavaScript
var db = firebase.database(); db.ref("records/rec1").once("value", function(snap) { // SUCCESS! }, function(err) { // error callback is not called });
Objective-C
FIRDatabaseReference *ref = [[FIRDatabase database] reference]; [[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) { // SUCCESS! }];
Swift
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!
Nakładające się oświadczenia
Do węzła może mieć zastosowanie więcej niż 1 reguła. Jeśli wiele wyrażeń reguł identyfikuje węzeł, metoda dostępu jest odrzucana, gdy dowolny z warunków ma wartość 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"
}
}
}
}W przykładzie powyżej odczyty węzła message1 będą odrzucane, ponieważ druga reguła zawsze ma wartość false, mimo że pierwsza reguła zawsze ma wartość true.
Dalsze kroki
Możesz dowiedzieć się więcej o regułach zabezpieczeń Bazy danych czasu rzeczywistego Firebase:
Poznaj kolejną ważną koncepcję języka Security Rules, czyli dynamiczne warunki, które pozwalają Security Rules sprawdzać autoryzację użytkownika, porównywać istniejące i przychodzące dane, weryfikować przychodzące dane, sprawdzać strukturę zapytań pochodzących od klienta i wykonywać inne czynności.
Zapoznaj się z typowymi przypadkami użycia związanymi z bezpieczeństwem i definicjami reguł zabezpieczeń Firebase, które ich dotyczą.