Poznaj podstawową składnię języka Realtime Database Security Rules

Reguły bezpieczeństwa bazy danych czasu rzeczywistego Firebase pozwalają kontrolować dostęp do danych przechowywanych w bazie danych. Elastyczna składnia reguł pozwala tworzyć reguły pasujące do wszystkiego, od wszystkich zapisów do bazy danych po operacje na poszczególnych węzłach.

Reguły bezpieczeństwa bazy danych w czasie rzeczywistym to deklaratywna konfiguracja bazy danych. Oznacza to, że reguły są definiowane oddzielnie od logiki produktu. Ma to wiele zalet: klienci nie są odpowiedzialni za egzekwowanie bezpieczeństwa, wadliwe implementacje nie narażają twoich danych, a 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ły zabezpieczeń bazy danych czasu rzeczywistego używane do tworzenia kompletnych zestawów reguł.

Strukturyzacja zasad bezpieczeństwa

Reguły bezpieczeństwa bazy danych czasu rzeczywistego składają się z wyrażeń podobnych do języka JavaScript zawartych w dokumencie JSON. Struktura Twoich reguł powinna być zgodna ze strukturą danych przechowywanych w Twojej bazie danych.

Podstawowe reguły identyfikują zestaw węzłów , które mają być zabezpieczone, stosowane 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 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 parent_node , ogólna składnia do naśladowania 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, które wyglądają tak:

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

Twoje zasady powinny mieć podobną strukturę. Oto zestaw reguł dotyczących zabezpieczeń tylko do odczytu, które mogą mieć sens w przypadku tej struktury danych. Ten przykład ilustruje, w jaki sposób 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"
      }
    }
  }
}

Operacje na zasadach podstawowych

Istnieją trzy typy reguł wymuszania bezpieczeństwa w zależności od typu 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 dane mogą być zapisywane.
.uprawomocnić Definiuje, jak będzie wyglądać prawidłowo 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ć określony węzeł lub używać zmiennych przechwytujących symboli wieloznacznych $ , aby wskazywać zestawy węzłów na poziomie hierarchii. Użyj tych zmiennych przechwytywania do przechowywania wartości kluczy węzłów do użycia 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 zapewnia, że widget nie ma elementów podrzędnych innych niż title i color . Każdy zapis, który spowodowałby utworzenie dodatkowych elementów podrzędnych, zakończyłby 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 }
    }
  }
}

Kaskada reguł odczytu i zapisu

Reguły .read i .write działają od góry do dołu, przy czym płytsze reguły zastępują głębsze reguły. Jeśli reguła przyznaje uprawnienia do odczytu lub zapisu w określonej ścieżce, przyznaje również dostęp do wszystkich węzłów podrzędnych pod nią. 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/ z każdego przypadku, gdy /foo/ zawiera baz potomną o wartości true . Reguła ".read": false w /foo/bar/ nie ma tu żadnego wpływu, ponieważ dostępu nie można cofnąć za pomocą ścieżki potomnej.

Chociaż może nie wydawać się to natychmiast 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 przejdziemy do zabezpieczeń opartych na użytkownikach w dalszej części tego przewodnika.

Zauważ, że reguły .validate nie są kaskadowane. Wszystkie reguły sprawdzania poprawności muszą być spełnione na wszystkich poziomach hierarchii, aby zapis był dozwolony.

Reguły nie są filtrami

Reguły są stosowane w sposób atomowy. Oznacza to, że operacja odczytu lub zapisu kończy się niepowodzeniem natychmiast, jeśli w tej lokalizacji lub w lokalizacji nadrzędnej nie ma reguły, która przyznaje dostęp. Nawet jeśli każda ścieżka podrzędna, której dotyczy problem, jest dostępna, 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, mogłoby 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
Uwaga: ten produkt Firebase nie jest dostępny w docelowym klipie aplikacji.
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
Uwaga: ten produkt Firebase nie jest dostępny w docelowym klipie aplikacji.
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 dostęp do wszystkich danych w /records/ , spowoduje to błąd 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 ścieżce, o którą prosiliśmy. 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
Uwaga: ten produkt Firebase nie jest dostępny w docelowym klipie aplikacji.
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
[[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
    // SUCCESS!
}];
Szybki
Uwaga: ten produkt Firebase nie jest dostępny w docelowym klipie aplikacji.
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!

Nakładające się stwierdzenia

Możliwe jest zastosowanie więcej niż jednej reguły do ​​węzła. W przypadku, gdy wiele wyrażeń reguł 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 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 czasu rzeczywistego Firebase:

  • 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, sprawdzać poprawność danych przychodzących, sprawdzać strukturę zapytań przychodzących od klienta i nie tylko.

  • Przejrzyj typowe przypadki użycia zabezpieczeń i odnoszące się do nich definicje Reguł bezpieczeństwa Firebase .