Reguły zabezpieczeń Bazy danych czasu rzeczywistego Firebase umożliwiają kontrolowanie dostępu do danych przechowywanych w bazie danych. Elastyczna składnia reguł pozwala tworzyć reguły, 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 oddzielnie od logiki usługi. Ma to wiele zalet: klienci nie są odpowiedzialni za egzekwowanie zabezpieczeń, błędne implementacje nie naruszą danych, a co najważniejsze, nie ma potrzeby korzystania z pośrednika, takiego jak serwer, aby chronić dane przed światem.
W tym artykule opisujemy 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 JavaScript, które znajdują się w dokumencie JSON. Struktura reguł powinna być zgodna ze strukturą danych przechowywanych w bazie danych.
Podstawowe reguły 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 artykule omówimy bardziej dynamiczne sposoby wyrażania warunków.
Jeśli na przykład próbujemy zabezpieczyć child_node w parent_node, ogólna składnia jest następująca:
{
"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
},
...
}
}Reguły powinny być ustrukturyzowane w podobny sposób. Oto zestaw reguł zabezpieczeń tylko do odczytu, które mogą być odpowiednie dla tej struktury danych. Ten przykład pokazuje, jak określamy węzły bazy danych, do których mają zastosowanie 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" } } } }
Podstawowe operacje na regułach
Istnieją 3 typy reguł egzekwowania zabezpieczeń na podstawie 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ć poprawnie 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ć zmiennych przechwytywania symboli wieloznacznych $, aby wskazywać zestawy węzłów na poziomie hierarchii. Użyj tych zmiennych przechwytywania, aby przechowywać wartość kluczy węzłów do użycia w kolejnych instrukcjach reguł. Ta technika pozwala pisać
bardziej złożone Security Rules warunki reguł zabezpieczeń, co omówimy bardziej szczegółowo
w następnym artykule.
{ "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')" } } } } }
Zmienne dynamiczne $ 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 elementów podrzędnych innych niż title i color.
Każda operacja zapisu, która spowodowałaby 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, a reguły na płytszych ścieżkach zastępują reguły na głębszych ścieżkach. 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 odczytywanie /bar/, gdy /foo/ zawiera element podrzędny baz o wartości true.
Reguła ".read": false w /foo/bar/ nie ma
wpływu, ponieważ dostęp nie może zostać cofnięty przez ścieżkę podrzędną.
Choć może się to wydawać nieintuicyjne, jest to ważna część języka reguł, która pozwala na implementowanie bardzo złożonych uprawnień dostępu przy minimalnym wysiłku. Zilustrujemy to, gdy w dalszej części tego przewodnika przejdziemy do zabezpieczeń opartych na użytkownikach.
Pamiętaj, że reguły .validate nie są kaskadowe. Aby zezwolić na zapis, wszystkie reguły sprawdzania poprawności muszą być spełnione na wszystkich poziomach hierarchii.
Reguły nie są filtrami
Reguły są stosowane w sposób atomowy. Oznacza to, że operacja odczytu lub zapisu zakończy się natychmiastowym niepowodzeniem, jeśli w tej lokalizacji lub w lokalizacji nadrzędnej nie ma reguły, która przyznaje dostęp. Nawet jeśli wszystkie elementy podrzędne, których dotyczy problem, są dostępne, odczyt w lokalizacji nadrzędnej zakończy się całkowitym niepowodzeniem. Rozważmy taką strukturę:
{
"rules": {
"records": {
"rec1": {
".read": true
},
"rec2": {
".read": false
}
}
}
}Jeśli nie wiemy, że reguły są oceniane atomowo, 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
Ponieważ operacja odczytu w /records/ jest atomowa i nie ma reguły odczytu, która przyznaje dostęp do wszystkich danych w /records/, spowoduje to błąd PERMISSION_DENIED. Jeśli ocenimy tę
regułę w symulatorze zabezpieczeń w naszej Firebase konsoli, zobaczymy, że
operacja odczytu została odrzucona, ponieważ żadna reguła odczytu nie zezwalała na dostęp do
/records/ ścieżki. Pamiętaj jednak, że reguła dla rec1 nigdy nie została oceniona, ponieważ nie znajdowała się w ścieżce, o którą prosiliśmy. Aby pobrać rec1, musimy uzyskać do niej 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ę instrukcje
Do węzła może mieć zastosowanie więcej niż 1 reguła. Jeśli więcej niż 1 wyrażenie reguły identyfikuje węzeł, metoda dostępu jest odrzucana, jeśli którykolwiek z warunków jest 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 powyższym przykładzie odczytywanie węzła message1 zostanie odrzucone, ponieważ druga reguła jest zawsze false, nawet jeśli pierwsza reguła jest zawsze true.
Dalsze kroki
Możesz pogłębić swoją wiedzę na temat reguł zabezpieczeń Bazy danych czasu rzeczywistego Firebase:
Poznaj kolejną ważną koncepcję języka Security Rules – warunki dynamiczne , które umożliwiają regułom Security Rules sprawdzanie autoryzacji użytkownika , porównywanie istniejących i przychodzących danych, sprawdzanie poprawności przychodzących danych, sprawdzanie struktury zapytań pochodzących od klienta i inne.
Zapoznaj się z typowymi przypadkami użycia zabezpieczeń i z definicjami reguł zabezpieczeń Firebase, które je rozwiązują.