Catch up on everything announced at Firebase Summit, and learn how Firebase can help you accelerate app development and run your app with confidence. Learn More

Bezpieczny dostęp do danych dla użytkowników i grup

Zadbaj o dobrą organizację dzięki kolekcji Zapisuj i kategoryzuj treści zgodnie ze swoimi preferencjami.

Wiele aplikacji do współpracy umożliwia użytkownikom odczytywanie i zapisywanie różnych fragmentów danych w oparciu o zestaw uprawnień. Na przykład w aplikacji do edycji dokumentów użytkownicy mogą zezwolić kilku użytkownikom na czytanie i zapisywanie ich dokumentów, jednocześnie blokując niepożądany dostęp.

Rozwiązanie: kontrola dostępu oparta na rolach

Możesz skorzystać z modelu danych Cloud Firestore oraz niestandardowych reguł bezpieczeństwa, aby zaimplementować kontrolę dostępu opartą na rolach w swojej aplikacji.

Załóżmy, że budujesz aplikację do wspólnego pisania, w której użytkownicy mogą tworzyć „historie” i „komentarze” z następującymi wymaganiami dotyczącymi bezpieczeństwa:

  • Każda historia ma jednego właściciela i może być udostępniana „pisarzom”, „komentatorom” i „czytelnikom”.
  • Czytelnicy widzą tylko artykuły i komentarze. Nie mogą niczego edytować.
  • Komentatorzy mają pełny dostęp czytelników, a także mogą dodawać komentarze do historii.
  • Pisarze mają pełny dostęp komentatorów, a także mogą edytować treść historii.
  • Właściciele mogą edytować dowolną część historii, a także kontrolować dostęp innych użytkowników.

Struktura danych

Załóżmy, że Twoja aplikacja ma kolekcję stories , w której każdy dokument reprezentuje narrację. Każda historia ma również podkolekcję comments , w której każdy dokument jest komentarzem do tej historii.

Aby śledzić role dostępu, dodaj pole roles , które jest mapą identyfikatorów użytkowników do ról:

/historie/{identyfikator opowieści}

{
  title: "A Great Story",
  content: "Once upon a time ...",
  roles: {
    alice: "owner",
    bob: "reader",
    david: "writer",
    jane: "commenter"
    // ...
  }
}

Komentarze zawierają tylko dwa pola, identyfikator użytkownika autora i trochę treści:

/historie/{identyfikator opowieści}/comments/{identyfikator komentarza}

{
  user: "alice",
  content: "I think this is a great story!"
}

Zasady

Teraz, gdy masz zarejestrowane role użytkowników w bazie danych, musisz napisać reguły bezpieczeństwa, aby je zweryfikować. Te reguły zakładają, że aplikacja używa Firebase Auth , więc zmienna request.auth.uid jest identyfikatorem użytkownika.

Krok 1 : Rozpocznij od podstawowego pliku reguł, który zawiera puste reguły dla historii i komentarzy:

service cloud.firestore {
   match /databases/{database}/documents {
     match /stories/{story} {
         // TODO: Story rules go here...

         match /comments/{comment} {
            // TODO: Comment rules go here...
         }
     }
   }
}

Krok 2 : Dodaj prostą regułę write , która daje właścicielom pełną kontrolę nad historiami. Zdefiniowane funkcje pomagają określić role użytkownika i czy nowe dokumenty są ważne:

service cloud.firestore {
   match /databases/{database}/documents {
     match /stories/{story} {
        function isSignedIn() {
          return request.auth != null;
        }

        function getRole(rsc) {
          // Read from the "roles" map in the resource (rsc).
          return rsc.data.roles[request.auth.uid];
        }

        function isOneOfRoles(rsc, array) {
          // Determine if the user is one of an array of roles
          return isSignedIn() && (getRole(rsc) in array);
        }

        function isValidNewStory() {
          // Valid if story does not exist and the new story has the correct owner.
          return resource == null && isOneOfRoles(request.resource, ['owner']);
        }

        // Owners can read, write, and delete stories
        allow write: if isValidNewStory() || isOneOfRoles(resource, ['owner']);

         match /comments/{comment} {
            // ...
         }
     }
   }
}

Krok 3 : Napisz reguły, które pozwolą użytkownikowi o dowolnej roli czytać historie i komentarze. Korzystanie z funkcji zdefiniowanych w poprzednim kroku sprawia, że ​​reguły są zwięzłe i czytelne:

service cloud.firestore {
   match /databases/{database}/documents {
     match /stories/{story} {
        function isSignedIn() {
          return request.auth != null;
        }

        function getRole(rsc) {
          return rsc.data.roles[request.auth.uid];
        }

        function isOneOfRoles(rsc, array) {
          return isSignedIn() && (getRole(rsc) in array);
        }

        function isValidNewStory() {
          return resource == null
            && request.resource.data.roles[request.auth.uid] == 'owner';
        }

        allow write: if isValidNewStory() || isOneOfRoles(resource, ['owner']);

        // Any role can read stories.
        allow read: if isOneOfRoles(resource, ['owner', 'writer', 'commenter', 'reader']);

        match /comments/{comment} {
          // Any role can read comments.
          allow read: if isOneOfRoles(get(/databases/$(database)/documents/stories/$(story)),
                                      ['owner', 'writer', 'commenter', 'reader']);
        }
     }
   }
}

Krok 4 : Zezwól autorom historii, komentatorom i właścicielom na publikowanie komentarzy. Zauważ, że ta reguła sprawdza również, czy owner komentarza jest zgodny z żądającym użytkownikiem, co uniemożliwia użytkownikom wzajemne zapisywanie komentarzy:

service cloud.firestore {
   match /databases/{database}/documents {
     match /stories/{story} {
        function isSignedIn() {
          return request.auth != null;
        }

        function getRole(rsc) {
          return rsc.data.roles[request.auth.uid];
        }

        function isOneOfRoles(rsc, array) {
          return isSignedIn() && (getRole(rsc) in array);
        }

        function isValidNewStory() {
          return resource == null
            && request.resource.data.roles[request.auth.uid] == 'owner';
        }

        allow write: if isValidNewStory() || isOneOfRoles(resource, ['owner'])
        allow read: if isOneOfRoles(resource, ['owner', 'writer', 'commenter', 'reader']);

        match /comments/{comment} {
          allow read: if isOneOfRoles(get(/databases/$(database)/documents/stories/$(story)),
                                      ['owner', 'writer', 'commenter', 'reader']);

          // Owners, writers, and commenters can create comments. The
          // user id in the comment document must match the requesting
          // user's id.
          //
          // Note: we have to use get() here to retrieve the story
          // document so that we can check the user's role.
          allow create: if isOneOfRoles(get(/databases/$(database)/documents/stories/$(story)),
                                        ['owner', 'writer', 'commenter'])
                        && request.resource.data.user == request.auth.uid;
        }
     }
   }
}

Krok 5 : Daj autorom możliwość edytowania treści historii, ale nie edytowania ról historii ani zmiany jakichkolwiek innych właściwości dokumentu. Wymaga to podzielenia reguły write historii na osobne reguły dla create , update i delete , ponieważ pisarze mogą tylko aktualizować historie:

service cloud.firestore {
   match /databases/{database}/documents {
     match /stories/{story} {
        function isSignedIn() {
          return request.auth != null;
        }

        function getRole(rsc) {
          return rsc.data.roles[request.auth.uid];
        }

        function isOneOfRoles(rsc, array) {
          return isSignedIn() && (getRole(rsc) in array);
        }

        function isValidNewStory() {
          return request.resource.data.roles[request.auth.uid] == 'owner';
        }

        function onlyContentChanged() {
          // Ensure that title and roles are unchanged and that no new
          // fields are added to the document.
          return request.resource.data.title == resource.data.title
            && request.resource.data.roles == resource.data.roles
            && request.resource.data.keys() == resource.data.keys();
        }

        // Split writing into creation, deletion, and updating. Only an
        // owner can create or delete a story but a writer can update
        // story content.
        allow create: if isValidNewStory();
        allow delete: if isOneOfRoles(resource, ['owner']);
        allow update: if isOneOfRoles(resource, ['owner'])
                      || (isOneOfRoles(resource, ['writer']) && onlyContentChanged());
        allow read: if isOneOfRoles(resource, ['owner', 'writer', 'commenter', 'reader']);

        match /comments/{comment} {
          allow read: if isOneOfRoles(get(/databases/$(database)/documents/stories/$(story)),
                                      ['owner', 'writer', 'commenter', 'reader']);
          allow create: if isOneOfRoles(get(/databases/$(database)/documents/stories/$(story)),
                                        ['owner', 'writer', 'commenter'])
                        && request.resource.data.user == request.auth.uid;
        }
     }
   }
}

Ograniczenia

Powyższe rozwiązanie demonstruje zabezpieczenie danych użytkownika za pomocą Reguł bezpieczeństwa, ale należy pamiętać o następujących ograniczeniach:

  • Szczegółowość : w powyższym przykładzie wiele ról (autor i właściciel) ma dostęp do zapisu do tego samego dokumentu, ale z różnymi ograniczeniami. Może to być trudne do zarządzania w przypadku bardziej złożonych dokumentów i może być lepiej podzielić pojedyncze dokumenty na wiele dokumentów, z których każdy należy do jednej roli.
  • Duże grupy : jeśli chcesz udostępniać dane bardzo dużym lub złożonym grupom, rozważ system, w którym role są przechowywane we własnej kolekcji, a nie jako pole w dokumencie docelowym.