Reguły bezpieczeństwa bazy danych Firebase Realtime pozwalają kontrolować dostęp do danych przechowywanych w bazie danych. Elastyczna składnia reguł umożliwia tworzenie reguł pasujących do wszystkiego, od wszystkich zapisów do bazy danych po operacje na poszczególnych węzłach.
Reguły zabezpieczeń bazy danych w czasie rzeczywistym są deklaratywną konfiguracją bazy danych. Oznacza to, że reguły definiowane są odrębnie od logiki produktu. Ma to wiele zalet: klienci nie są odpowiedzialni za egzekwowanie bezpieczeństwa, błędne implementacje nie naruszą bezpieczeństwa danych, a co być może najważniejsze, nie ma potrzeby korzystania z pośrednika, takiego jak serwer, w celu ochrony danych przed światem.
W tym temacie opisano podstawową składnię i strukturę Reguły zabezpieczeń bazy danych czasu rzeczywistego używane do tworzenia kompletnych zestawów reguł.
Struktura reguł bezpieczeństwa
Reguły bezpieczeństwa bazy danych w czasie rzeczywistym składają się z wyrażeń przypominających JavaScript zawartych w dokumencie JSON. Struktura reguł powinna być zgodna ze strukturą danych przechowywanych w bazie danych.
Podstawowe reguły identyfikują zbiór węzłów, które mają być zabezpieczone, stosowane metody dostępu (np. odczyt, zapis) oraz warunki , pod którymi dostęp jest dozwolony lub zabroniony. W poniższych przykładach nasze warunki będą prostymi stwierdzeniami true
i false
, ale w następnym temacie omówimy bardziej dynamiczne sposoby wyrażania warunków.
Na przykład, jeśli próbujemy zabezpieczyć child_node
pod węzłem parent_node
, ogólna składnia, którą należy zastosować, jest następująca:
{ "rules": { "parent_node": { "child_node": { ".read": <condition>, ".write": <condition>, ".validate": <condition>, } } } }
Zastosujmy ten wzór. Załóżmy na przykład, że śledzisz listę wiadomości i masz dane wyglądające tak:
{ "messages": { "message0": { "content": "Hello", "timestamp": 1405704370369 }, "message1": { "content": "Goodbye", "timestamp": 1405704395231 }, ... } }
Twoje zasady powinny być skonstruowane w podobny sposób. Oto zestaw reguł zabezpieczeń tylko do odczytu, które mogą mieć sens w przypadku tej struktury danych. Ten przykład ilustruje, 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 zasady operacji
Istnieją trzy typy reguł wymuszania bezpieczeństwa w zależności od rodzaju operacji wykonywanej na danych: .write
, .read
i .validate
. Oto krótkie podsumowanie ich celów:
Typy reguł | |
---|---|
.Czytać | Opisuje, czy i kiedy dane mogą być odczytywane przez użytkowników. |
.pisać | Opisuje, czy i kiedy można zapisywać dane. |
.uprawomocnić | Określa, jak będzie wyglądać poprawnie sformatowana wartość, czy ma atrybuty podrzędne i typ danych. |
Zmienne przechwytywania symboli wieloznacznych
Wszystkie instrukcje reguł wskazują na węzły. Instrukcja może wskazywać konkretny węzeł lub używać zmiennych przechwytujących symbole wieloznaczne $
, aby wskazywać zestawy węzłów na poziomie hierarchii. Użyj tych zmiennych przechwytujących do przechowywania wartości kluczy węzłów do wykorzystania w kolejnych instrukcjach reguł. Ta technika umożliwia pisanie bardziej złożonych warunków Reguł, 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')" } } } } }
Dynamiczne zmienne $
mogą być również używane równolegle ze stałymi nazwami ścieżek. W tym przykładzie używamy zmiennej $other
do zadeklarowania reguły .validate
, która gwarantuje, że widget
nie będzie miał żadnych dzieci poza title
i color
. Jakikolwiek zapis, który spowodowałby utworzenie dodatkowych dzieci, 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 } } } }
Odczytuj i zapisuj reguły kaskadowo
Reguły .read
i .write
działają od góry do dołu, przy czym płytsze reguły zastępują głębsze. Jeśli reguła przyznaje uprawnienia do odczytu lub zapisu w określonej ścieżce, wówczas zapewnia także dostęp do wszystkich znajdujących się pod nią węzłów podrzędnych. Rozważ następującą 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 /bar/
za każdym razem, gdy /foo/
zawiera baz
podrzędną o wartości true
. ".read": false
w /foo/bar/
nie ma tutaj żadnego efektu, ponieważ ścieżka podrzędna nie może odwołać dostępu.
Chociaż może nie wydawać się to od razu intuicyjne, jest to potężna część języka reguł i pozwala na wdrożenie bardzo złożonych uprawnień dostępu przy minimalnym wysiłku. Zostanie to zilustrowane, gdy zajmiemy się bezpieczeństwem opartym na użytkownikach w dalszej części tego przewodnika.
Należy pamiętać, że reguły .validate
nie łączą się kaskadowo. 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 atomowy. Oznacza to, że operacja odczytu lub zapisu kończy się natychmiastowym niepowodzeniem, jeśli w tej lokalizacji lub w lokalizacji nadrzędnej nie ma reguły udzielającej dostępu. Nawet jeśli dostępna jest każda ścieżka podrzędna, której dotyczy problem, odczyt w lokalizacji nadrzędnej zakończy się całkowitym niepowodzeniem. Rozważ tę strukturę:
{ "rules": { "records": { "rec1": { ".read": true }, "rec2": { ".read": false } } } }
Bez zrozumienia, że reguły są oceniane atomowo, może się wydawać, że pobranie ścieżki /records/
zwróci rec1
, ale nie rec2
. Rzeczywisty wynik jest jednak błędem:
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 });
Cel 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 }];
Szybki
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 })
Jawa
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 }); });
ODPOCZYNEK
curl https://docs-examples.firebaseio.com/rest/records/ # response returns a PERMISSION_DENIED error
Ponieważ operacja odczytu w /records/
jest niepodzielna i nie ma reguły odczytu, która zapewniałaby dostęp do wszystkich danych w /records/
, spowoduje to wyświetlenie błędu PERMISSION_DENIED
. Jeśli ocenimy tę regułę w symulatorze bezpieczeństwa w naszej konsoli Firebase , zobaczymy, że operacja odczytu została odrzucona, ponieważ żadna reguła odczytu nie zezwalała na dostęp do ścieżki /records/
. Należy jednak pamiętać, że reguła rec1
nigdy nie została oceniona, ponieważ nie znajdowała się w żądanej ścieżce. Aby pobrać rec1
, musielibyśmy 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 });
Cel C
FIRDatabaseReference *ref = [[FIRDatabase database] reference]; [[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) { // SUCCESS! }];
Szybki
var ref = FIRDatabase.database().reference() ref.child("records/rec1").observeSingleEventOfType(.Value, withBlock: { snapshot in // SUCCESS! })
Jawa
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 } });
ODPOCZYNEK
curl https://docs-examples.firebaseio.com/rest/records/rec1 # SUCCESS!
Pokrywające się stwierdzenia
Do węzła można zastosować więcej niż jedną regułę. W przypadku, gdy węzeł identyfikuje wiele wyrażeń reguł, metoda dostępu zostaje odrzucona, 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 odczyty do węzła message1
zostaną odrzucone, ponieważ druga reguła jest zawsze false
, mimo że pierwsza reguła jest zawsze true
.
Następne kroki
Możesz pogłębić swoją wiedzę na temat reguł bezpieczeństwa bazy danych Firebase Realtime:
Poznaj kolejną ważną koncepcję języka Reguł, warunków dynamicznych, które pozwalają Regułom sprawdzać autoryzację użytkownika, porównywać istniejące i przychodzące dane, weryfikować przychodzące dane, sprawdzać strukturę zapytań przychodzących od klienta i nie tylko.
Przejrzyj typowe przypadki użycia zabezpieczeń i definicje reguł bezpieczeństwa Firebase, które je dotyczą .