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

Reguły bezpieczeństwa bazy danych czasu rzeczywistego Firebase umożliwiają kontrolę dostępu do danych przechowywanych w bazie danych. Elastyczna składnia reguł umożliwia tworzenie reguł, które pasują do wszystkiego, od wszystkich zapisów do bazy danych po operacje na poszczególnych węzłach.

Reguły bezpieczeństwa bazy danych czasu rzeczywistego to deklaratywna konfiguracja bazy danych. Oznacza to, że reguły są definiowane niezależnie od logiki produktu. Ma to wiele zalet: klienci nie są odpowiedzialni za egzekwowanie bezpieczeństwa, wadliwe implementacje nie narażają danych, a co najważniejsze, nie ma potrzeby pośredniego referenta, takiego jak serwer, aby chronić dane przed światem.

W tym temacie opisano podstawową składnię i strukturę Reguły bezpieczeństwa bazy danych czasu rzeczywistego używane do tworzenia kompletnych zestawów reguł.

Struktura zasad bezpieczeństwa

Reguły bezpieczeństwa bazy danych czasu rzeczywistego składają się z wyrażeń podobnych do JavaScript zawartych w dokumencie JSON. Struktura Twoich reguł powinna być zgodna ze strukturą danych, które przechowujesz w swojej bazie danych.

Podstawowe zasady określają 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 typu 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 to:

{
  "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 być skonstruowane w podobny sposób. Oto zestaw reguł dotyczących zabezpieczeń tylko do odczytu, które mogą mieć sens dla 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"
      }
    }
  }
}

Podstawowe zasady działania

Istnieją trzy typy reguł egzekwowania zabezpieczeń 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 użytkownicy mogą odczytywać dane.
.pisać Opisuje, czy i kiedy można zapisywać dane.
.uprawomocnić Definiuje, 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ć na określony węzeł lub używać zmiennych przechwytywania symboli wieloznacznych $ do wskazywania zestawów 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 innych elementów podrzędnych poza title i color . Każdy zapis, który skutkowałby utworzeniem dodatkowych dzieci, 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 }
    }
  }
}

Czytaj i zapisuj zasady kaskadowo

Reguły .write .read od góry do dołu, przy czym płytsze reguły mają pierwszeństwo przed głębszymi regułami. Jeśli reguła przyznaje uprawnienia do odczytu lub zapisu w określonej ścieżce, zapewnia 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 bezpieczeństwa pozwala na odczytanie /bar/ z każdego miejsca, w którym /foo/ zawiera potomny baz z wartością true . ".read": false reguła w /foo/bar/ nie ma tu żadnego efektu, ponieważ dostępu nie można cofnąć przez ścieżkę podrzędną.

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.

Pamiętaj, że reguły .validate nie są kaskadowe. Aby zapis był dozwolony, wszystkie reguły walidacji 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 kończy się niepowodzeniem natychmiast, jeśli w tej lokalizacji lub w lokalizacji nadrzędnej nie ma reguły, która udziela dostępu. Nawet jeśli każda ścieżka podrzędna, której dotyczy problem, jest dostępna, odczyt w lokalizacji nadrzędnej całkowicie się nie powiedzie. Rozważ tę strukturę:

{
  "rules": {
    "records": {
      "rec1": {
        ".read": true
      },
      "rec2": {
        ".read": false
      }
    }
  }
}

Bez zrozumienia, że ​​reguły są oceniane niepodzielnie, 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 miejscu docelowym klipu 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 miejscu docelowym klipu 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 przyznałaby dostęp do wszystkich danych w /records/ , spowoduje to zgłoszenie błędu PERMISSION_DENIED . Jeśli ocenimy tę regułę w symulatorze bezpieczeństwa 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/ . Należy jednak pamiętać, że reguła dla 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
Uwaga: ten produkt Firebase nie jest dostępny w miejscu docelowym klipu 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 miejscu docelowym klipu 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ę oświadczenia

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 będą odrzucane, 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ą główną koncepcję języka Reguły, dynamiczne warunki , które pozwalają Regułom sprawdzać autoryzację użytkowników, 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ń oraz definicje reguł zabezpieczeń Firebase, które je dotyczą .