Warunki korzystania z reguł bazy danych czasu rzeczywistego

Podręcznik ten opiera się na nauczyć się podstawowej zasady Firebase zabezpieczeń język instrukcji, aby pokazać, jak dodać warunki do Firebase Realtime Database zasad bezpieczeństwa.

Podstawowym budulcem Realtime Database zasad bezpieczeństwa jest warunkiem. Warunek to wyrażenie logiczne, które określa, czy dana operacja powinna być dozwolona, ​​czy zabroniona. Do podstawowych zasad, używając true i false literały w warunkach działa perfekcyjnie dobrze. Ale język reguł bezpieczeństwa bazy danych w czasie rzeczywistym umożliwia pisanie bardziej złożonych warunków, które mogą:

  • Sprawdź uwierzytelnianie użytkownika
  • Oceń istniejące dane z nowo przesłanymi danymi
  • Uzyskaj dostęp i porównaj różne części swojej bazy danych
  • Sprawdź poprawność danych przychodzących
  • Użyj struktury przychodzących zapytań do logiki bezpieczeństwa

Używanie zmiennych $ do przechwytywania segmentów ścieżki

Można przechwytywać fragmenty ścieżki do odczytu lub zapisu przez deklarowania zmiennych przechwytywania z $ prefix. Służy jako dzika karta i przechowuje wartość tego klucza do użycia w warunkach reguł:

{
  "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')"
        }
      }
    }
  }
}

Te dynamiczne $ zmienne mogą być również wykorzystywane równolegle ze stałych nazw ścieżek. W tym przykładzie używamy $other zmienną zadeklarować .validate regułę , która zapewnia, że widget ma dzieci innych niż 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 }
    }
  }
}

Poświadczenie

Jednym z najczęstszych wzorców reguł bezpieczeństwa jest kontrolowanie dostępu na podstawie stanu uwierzytelniania użytkownika. Na przykład Twoja aplikacja może chcieć zezwalać tylko zalogowanym użytkownikom na zapisywanie danych.

Jeśli aplikacja korzysta z uwierzytelniania Firebase The request.auth zmienna zawiera informacje uwierzytelniania klienta wzywającej dane. Aby uzyskać więcej informacji na temat request.auth , zobacz dokumentację referencyjną .

Uwierzytelnianie Firebase integruje się z bazą danych czasu rzeczywistego Firebase, aby umożliwić kontrolowanie dostępu do danych dla poszczególnych użytkowników przy użyciu warunków. Raz w uwierzytelnia użytkownika The auth zmienna w swoich zasad bezpieczeństwa w czasie rzeczywistym bazy danych zasad zostanie wypełniona informacjami użytkownika. Informacja ta obejmuje ich unikalny identyfikator ( uid ), jak również połączonych danych konta, takich jak id Facebooku lub adres e-mail i innych informacji. Jeśli zaimplementujesz niestandardowego dostawcę uwierzytelniania, możesz dodać własne pola do ładunku uwierzytelniania użytkownika.

W tej sekcji wyjaśniono, jak połączyć język reguł zabezpieczeń bazy danych czasu rzeczywistego Firebase z informacjami uwierzytelniającymi użytkowników. Łącząc te dwie koncepcje, możesz kontrolować dostęp do danych na podstawie tożsamości użytkownika.

auth Variable

Predefiniowanych auth zmienna w przepisach jest null przed uwierzytelnianie odbywa.

Gdy użytkownik jest uwierzytelniony z uwierzytelnianiem Firebase będzie zawierać następujące atrybuty:

dostawca Użyta metoda uwierzytelniania („hasło”, „anonimowy”, „facebook”, „github”, „google” lub „twitter”).
uid Unikalny identyfikator użytkownika, gwarantujący, że będzie unikalny dla wszystkich dostawców.
znak Zawartość tokena Firebase Auth ID. Zobacz dokumentację referencyjną auth.token więcej szczegółów.

Oto przykład zasada, że używa auth zmienną, aby zapewnić, że każdy użytkownik może napisać tylko do ścieżki określonej przez użytkownika:

{
  "rules": {
    "users": {
      "$user_id": {
        // grants write access to the owner of this user account
        // whose uid must exactly match the key ($user_id)
        ".write": "$user_id === auth.uid"
      }
    }
  }
}

Strukturyzacja bazy danych w celu obsługi warunków uwierzytelniania

Zwykle pomocne jest zorganizowanie bazy danych w sposób, który ułatwia pisanie Reguł. Jeden wspólny wzorzec do przechowywania danych użytkownika w bazie danych w czasie rzeczywistym jest przechowywanie wszystkich użytkowników w jednym users węzła, którego dzieci są uid wartości dla każdego użytkownika. Jeśli chciałbyś ograniczyć dostęp do tych danych tak, aby tylko zalogowany użytkownik mógł zobaczyć własne dane, Twoje reguły wyglądałyby mniej więcej tak.

{
  "rules": {
    "users": {
      "$uid": {
        ".read": "auth != null && auth.uid == $uid"
      }
    }
  }
}

Praca z niestandardowymi oświadczeniami uwierzytelniania

W przypadku aplikacji, które wymagają niestandardowego kontroli dostępu dla różnych użytkowników, uwierzytelniania Firebase umożliwia programistom zestaw zastrzeżeń dotyczących użytkownika Firebase . Twierdzenia te są dostepne w auth.token zmiennej w swoich zasadach. Oto przykład z zasad, które korzystają z hasEmergencyTowel zastrzeżeniu niestandardowego:

{
  "rules": {
    "frood": {
      // A towel is about the most massively useful thing an interstellar
      // hitchhiker can have
      ".read": "auth.token.hasEmergencyTowel === true"
    }
  }
}

Programiści tworzenia własnych znaki uwierzytelniania zwyczaj można opcjonalnie dodać zastrzeżeń do tych słów. Twierdzenia te są dostepne na auth.token zmiennej w swoich zasadach.

Istniejące dane a nowe dane

Predefiniowanych data zmienna jest używany w odniesieniu do danych przed operacją zapisu odbywa. Odwrotnie, newData zmienna zawiera nowe dane, które będą występować, jeśli operacja zapisu jest udany. newData reprezentuje połączonego wyniku nowe dane są zapisywane i istniejące dane.

Aby to zilustrować, ta reguła pozwoliłaby nam tworzyć nowe rekordy lub usuwać istniejące, ale nie wprowadzać zmian w istniejących danych innych niż null:

// we can write as long as old data or new data does not exist
// in other words, if this is a delete or a create, but not an update
".write": "!data.exists() || !newData.exists()"

Odwoływanie się do danych w innych ścieżkach

Dowolne dane mogą służyć jako kryterium reguł. Korzystanie z predefiniowanych zmiennych root , data i newData , możemy uzyskać dostęp do dowolnej ścieżki gdyż istnieje przed lub po zdarzeniu zapisu.

Rozważmy następujący przykład, który umożliwia operacje zapisu, o ile wartości z /allow_writes/ węzeł jest true , węzeł rodzic nie posiada readOnly flagę, a tam jest dziecko o nazwie foo w nowo zapisanych danych:

".write": "root.child('allow_writes').val() === true &&
          !data.parent().child('readOnly').exists() &&
          newData.child('foo').exists()"

Weryfikowanie danych

Egzekwowanie struktur danych i sprawdzania formatu i zawartości danych powinno odbywać się za pomocą .validate zasady, które są uruchamiane dopiero po .write reguły udaje się do udzielenia dostępu. Poniżej znajduje się przykładowy .validate definicja zasada, która pozwala tylko na datę w formacie RRRR-MM-DD w latach 1900-2099, która jest sprawdzana za pomocą wyrażenia regularnego.

".validate": "newData.isString() &&
              newData.val().matches(/^(19|20)[0-9][0-9][-\\/. ](0[1-9]|1[012])[-\\/. ](0[1-9]|[12][0-9]|3[01])$/)"

W .validate zasady są jedynym rodzajem reguły bezpieczeństwa, które nie kaskadę. Jeśli jakakolwiek reguła walidacji nie powiedzie się w dowolnym rekordzie podrzędnym, cała operacja zapisu zostanie odrzucona. Dodatkowo definicje walidacji są ignorowane, gdy dane są usuwane (to znaczy, gdy nowa wartość jest napisane jest null ).

Może się to wydawać trywialne, ale w rzeczywistości są one ważnymi funkcjami do pisania potężnych reguł zabezpieczeń bazy danych czasu rzeczywistego Firebase. Rozważ następujące zasady:

{
  "rules": {
    // write is allowed for all paths
    ".write": true,
    "widget": {
      // a valid widget must have attributes "color" and "size"
      // allows deleting widgets (since .validate is not applied to delete rules)
      ".validate": "newData.hasChildren(['color', 'size'])",
      "size": {
        // the value of "size" must be a number between 0 and 99
        ".validate": "newData.isNumber() &&
                      newData.val() >= 0 &&
                      newData.val() <= 99"
      },
      "color": {
        // the value of "color" must exist as a key in our mythical
        // /valid_colors/ index
        ".validate": "root.child('valid_colors/' + newData.val()).exists()"
      }
    }
  }
}

Mając na uwadze ten wariant, spójrz na wyniki następujących operacji zapisu:

JavaScript
var ref = db.ref("/widget");

// PERMISSION_DENIED: does not have children color and size
ref.set('foo');

// PERMISSION DENIED: does not have child color
ref.set({size: 22});

// PERMISSION_DENIED: size is not a number
ref.set({ size: 'foo', color: 'red' });

// SUCCESS (assuming 'blue' appears in our colors list)
ref.set({ size: 21, color: 'blue'});

// If the record already exists and has a color, this will
// succeed, otherwise it will fail since newData.hasChildren(['color', 'size'])
// will fail to validate
ref.child('size').set(99);
Cel C
FIRDatabaseReference *ref = [[[FIRDatabase database] reference] child: @"widget"];

// PERMISSION_DENIED: does not have children color and size
[ref setValue: @"foo"];

// PERMISSION DENIED: does not have child color
[ref setValue: @{ @"size": @"foo" }];

// PERMISSION_DENIED: size is not a number
[ref setValue: @{ @"size": @"foo", @"color": @"red" }];

// SUCCESS (assuming 'blue' appears in our colors list)
[ref setValue: @{ @"size": @21, @"color": @"blue" }];

// If the record already exists and has a color, this will
// succeed, otherwise it will fail since newData.hasChildren(['color', 'size'])
// will fail to validate
[[ref child:@"size"] setValue: @99];
Szybki
var ref = FIRDatabase.database().reference().child("widget")

// PERMISSION_DENIED: does not have children color and size
ref.setValue("foo")

// PERMISSION DENIED: does not have child color
ref.setValue(["size": "foo"])

// PERMISSION_DENIED: size is not a number
ref.setValue(["size": "foo", "color": "red"])

// SUCCESS (assuming 'blue' appears in our colors list)
ref.setValue(["size": 21, "color": "blue"])

// If the record already exists and has a color, this will
// succeed, otherwise it will fail since newData.hasChildren(['color', 'size'])
// will fail to validate
ref.child("size").setValue(99);
Jawa
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference("widget");

// PERMISSION_DENIED: does not have children color and size
ref.setValue("foo");

// PERMISSION DENIED: does not have child color
ref.child("size").setValue(22);

// PERMISSION_DENIED: size is not a number
Map<String,Object> map = new HashMap<String, Object>();
map.put("size","foo");
map.put("color","red");
ref.setValue(map);

// SUCCESS (assuming 'blue' appears in our colors list)
map = new HashMap<String, Object>();
map.put("size", 21);
map.put("color","blue");
ref.setValue(map);

// If the record already exists and has a color, this will
// succeed, otherwise it will fail since newData.hasChildren(['color', 'size'])
// will fail to validate
ref.child("size").setValue(99);
RESZTA
# PERMISSION_DENIED: does not have children color and size
curl -X PUT -d 'foo' \
https://docs-examples.firebaseio.com/rest/securing-data/example.json

# PERMISSION DENIED: does not have child color
curl -X PUT -d '{"size": 22}' \
https://docs-examples.firebaseio.com/rest/securing-data/example.json

# PERMISSION_DENIED: size is not a number
curl -X PUT -d '{"size": "foo", "color": "red"}' \
https://docs-examples.firebaseio.com/rest/securing-data/example.json

# SUCCESS (assuming 'blue' appears in our colors list)
curl -X PUT -d '{"size": 21, "color": "blue"}' \
https://docs-examples.firebaseio.com/rest/securing-data/example.json

# If the record already exists and has a color, this will
# succeed, otherwise it will fail since newData.hasChildren(['color', 'size'])
# will fail to validate
curl -X PUT -d '99' \
https://docs-examples.firebaseio.com/rest/securing-data/example/size.json

Teraz spójrzmy na tej samej konstrukcji, ale przy użyciu .write zasady zamiast .validate :

{
  "rules": {
    // this variant will NOT allow deleting records (since .write would be disallowed)
    "widget": {
      // a widget must have 'color' and 'size' in order to be written to this path
      ".write": "newData.hasChildren(['color', 'size'])",
      "size": {
        // the value of "size" must be a number between 0 and 99, ONLY IF WE WRITE DIRECTLY TO SIZE
        ".write": "newData.isNumber() && newData.val() >= 0 && newData.val() <= 99"
      },
      "color": {
        // the value of "color" must exist as a key in our mythical valid_colors/ index
        // BUT ONLY IF WE WRITE DIRECTLY TO COLOR
        ".write": "root.child('valid_colors/'+newData.val()).exists()"
      }
    }
  }
}

W tym wariancie powiodła się dowolna z następujących operacji:

JavaScript
var ref = new Firebase(URL + "/widget");

// ALLOWED? Even though size is invalid, widget has children color and size,
// so write is allowed and the .write rule under color is ignored
ref.set({size: 99999, color: 'red'});

// ALLOWED? Works even if widget does not exist, allowing us to create a widget
// which is invalid and does not have a valid color.
// (allowed by the write rule under "color")
ref.child('size').set(99);
Cel C
Firebase *ref = [[Firebase alloc] initWithUrl:URL];

// ALLOWED? Even though size is invalid, widget has children color and size,
// so write is allowed and the .write rule under color is ignored
[ref setValue: @{ @"size": @9999, @"color": @"red" }];

// ALLOWED? Works even if widget does not exist, allowing us to create a widget
// which is invalid and does not have a valid color.
// (allowed by the write rule under "color")
[[ref childByAppendingPath:@"size"] setValue: @99];
Szybki
var ref = Firebase(url:URL)

// ALLOWED? Even though size is invalid, widget has children color and size,
// so write is allowed and the .write rule under color is ignored
ref.setValue(["size": 9999, "color": "red"])

// ALLOWED? Works even if widget does not exist, allowing us to create a widget
// which is invalid and does not have a valid color.
// (allowed by the write rule under "color")
ref.childByAppendingPath("size").setValue(99)
Jawa
Firebase ref = new Firebase(URL + "/widget");

// ALLOWED? Even though size is invalid, widget has children color and size,
// so write is allowed and the .write rule under color is ignored
Map<String,Object> map = new HashMap<String, Object>();
map.put("size", 99999);
map.put("color", "red");
ref.setValue(map);

// ALLOWED? Works even if widget does not exist, allowing us to create a widget
// which is invalid and does not have a valid color.
// (allowed by the write rule under "color")
ref.child("size").setValue(99);
RESZTA
# ALLOWED? Even though size is invalid, widget has children color and size,
# so write is allowed and the .write rule under color is ignored
curl -X PUT -d '{size: 99999, color: "red"}' \
https://docs-examples.firebaseio.com/rest/securing-data/example.json

# ALLOWED? Works even if widget does not exist, allowing us to create a widget
# which is invalid and does not have a valid color.
# (allowed by the write rule under "color")
curl -X PUT -d '99' \
https://docs-examples.firebaseio.com/rest/securing-data/example/size.json

To ilustruje różnice między .write i .validate zasad. Jak wykazano, wszystkie z tych zasad powinno być napisane przy użyciu .validate , z możliwym wyjątkiem newData.hasChildren() zasady, które zależą od tego, czy delecje powinno być dozwolone.

Zasady oparte na zapytaniach

Mimo, że nie można korzystać z reguły jako filtry , można ograniczyć dostęp do podzbiorów danych za pomocą parametrów zapytania w swoich zasadach. Zastosowanie query. wyrażenia w regułach, aby przyznać dostęp do odczytu lub zapisu na podstawie parametrów zapytania.

Na przykład następująca reguła oparta na zapytaniach stosuje zasady bezpieczeństwa użytkownika w oparciu i zasady kwerend opartych na ograniczenie dostępu do danych w baskets kolekcji tylko do koszyki aktywny użytkownik jest właścicielem:

"baskets": {
  ".read": "auth.uid != null &&
            query.orderByChild == 'owner' &&
            query.equalTo == auth.uid" // restrict basket access to owner of basket
}

Następujące zapytanie, które zawiera parametry zapytania w regule, powiodłoby się:

db.ref("baskets").orderByChild("owner")
                 .equalTo(auth.currentUser.uid)
                 .on("value", cb)                 // Would succeed

Jednak pytania, które nie zawierają parametry w regule nie powiedzie się z PermissionDenied błędu:

db.ref("baskets").on("value", cb)                 // Would fail with PermissionDenied

Możesz również użyć reguł opartych na zapytaniach, aby ograniczyć ilość danych pobieranych przez klienta za pomocą operacji odczytu.

Na przykład poniższa reguła ogranicza dostęp do odczytu tylko do pierwszych 1000 wyników zapytania, uporządkowanych według priorytetu:

messages: {
  ".read": "query.orderByKey &&
            query.limitToFirst <= 1000"
}

// Example queries:

db.ref("messages").on("value", cb)                // Would fail with PermissionDenied

db.ref("messages").limitToFirst(1000)
                  .on("value", cb)                // Would succeed (default order by key)

Następująca query. wyrażenia są dostępne w regułach bezpieczeństwa bazy danych czasu rzeczywistego.

Wyrażenia reguł oparte na zapytaniach
Wyrażenie Rodzaj Opis
zapytanie.zamówienieByKey
zapytanie.orderByPriority
zapytanie.orderByValue
logiczne Prawda dla zapytań uporządkowanych według klucza, priorytetu lub wartości. W przeciwnym razie fałsz.
zapytanie.orderByChild strunowy
zero
Użyj ciągu do reprezentowania ścieżki względnej do węzła podrzędnego. Na przykład, query.orderByChild == "address/zip" . Jeśli zapytanie nie jest uporządkowane przez węzeł podrzędny, ta wartość ma wartość null.
zapytanie.startAt
zapytanie.endAt
zapytanie.equalTo
strunowy
numer
logiczne
zero
Pobiera granice wykonywanego zapytania lub zwraca wartość null, jeśli nie ma zestawu ograniczeń.
zapytanie.limitToFirst
zapytanie.limitToLast
numer
zero
Pobiera limit wykonywanego zapytania lub zwraca wartość null, jeśli nie ustawiono limitu.

Następne kroki

Po tym omówieniu warunków masz bardziej wyrafinowane zrozumienie Reguł i jesteś gotowy do:

Dowiedz się, jak radzić sobie z podstawowymi przypadkami użycia i poznaj przepływ pracy podczas opracowywania, testowania i wdrażania reguł:

Poznaj funkcje reguł, które są specyficzne dla Bazy danych czasu rzeczywistego: