Bezpieczne zapytania o dane

Na tej stronie znajdziesz informacje o tym, jak Cloud Firestore Security Rules współdziałają z zapytaniami. Opierają się one na pojęciach opisanych w artykułach Struktura reguł zabezpieczeń i Pisanie warunków reguł zabezpieczeń. Przyjrzymy się bliżej temu, jak reguły zabezpieczeń wpływają na zapytania, które możesz pisać, i opiszemy, jak zapewnić, że zapytania będą używać tych samych ograniczeń co reguły zabezpieczeń. Na tej stronie opisano też, jak pisać reguły zabezpieczeń, aby zezwalać na zapytania lub je odrzucać na podstawie właściwości zapytania, takich jak limit i orderBy.

Aby uzyskać pomoc AI, skopiuj poniższy prompt do agenta kodowania AI (takiego jak Gemini w Android Studio, Antigravity, Claude Code, Codex czy Cursor). Możesz dostosować prompta i wprowadzać zmiany z agentem. Szczegółowe informacje o instalacji znajdziesz w artykule Umiejętności agenta Firebase.

Help me use Firebase in my app.
1. Make sure that **Firebase agent skills** are installed and updated. For Claude Code, Codex, or Cursor agents, help me install the Firebase plugin. For other agents, run `npx skills add firebase/agent-skills`. Always try to use **Firebase agent skills** for Firebase-related tasks.
2. Help me write, validate, and deploy Firebase Security Rules to protect my app's Firestore data.

Reguły to nie filtry

Pisząc zapytania do pobierania dokumentów, pamiętaj, że reguły zabezpieczeń nie są filtrami – zapytania działają na zasadzie „wszystko albo nic”. Aby zaoszczędzić czas i zasoby, Reguły oceniają zapytanie na podstawie potencjalnego zestawu wyników, a nie rzeczywistych wartości pól we wszystkich dokumentach. Jeśli zapytanie może potencjalnie zwrócić dokumenty, do których klient nie ma uprawnień do odczytu, całe żądanie zakończy się niepowodzeniem.

Zapytania i reguły zabezpieczeń

Jak pokazują poniższe przykłady, zapytania muszą być napisane tak, aby spełniały ograniczenia reguł zabezpieczeń.

Zabezpieczanie dokumentów i tworzenie zapytań na podstawie auth.uid

Poniższy przykład pokazuje, jak napisać zapytanie, aby pobrać dokumenty chronione przez regułę zabezpieczeń. Rozważmy bazę danych zawierającą kolekcję dokumentów story:

/stories/{storyid}

{
  title: "A Great Story",
  content: "Once upon a time...",
  author: "some_auth_id",
  published: false
}

Oprócz pól title i content każdy dokument zawiera pola author i published, które służą do kontroli dostępu. W tych przykładach zakładamy, że aplikacja używa Uwierzytelniania Firebase do ustawiania pola author na identyfikator UID użytkownika, który utworzył dokument. Uwierzytelnianie Firebase wypełnia też zmienną request.auth w regułach zabezpieczeń.

Ta reguła zabezpieczeń używa zmiennych request.auth i resource.data, aby ograniczyć uprawnienia do odczytu i zapisu każdego elementu story do jego autora:

service cloud.firestore {
  match /databases/{database}/documents {
    match /stories/{storyid} {
      // Only the authenticated user who authored the document can read or write
      allow read, write: if request.auth != null && request.auth.uid == resource.data.author;
    }
  }
}

Załóżmy, że Twoja aplikacja zawiera stronę, na której użytkownik widzi listę dokumentów story, których jest autorem. Można by się spodziewać, że do wypełnienia tej strony można użyć tego zapytania. To zapytanie nie powiedzie się, ponieważ nie zawiera tych samych ograniczeń co reguły zabezpieczeń:

Nieprawidłowe: ograniczenia zapytania nie pasują do ograniczeń reguł zabezpieczeń.

// This query will fail
db.collection("stories").get()

Zapytanie nie powiedzie się, nawet jeśli bieżący użytkownik jest autorem każdego dokumentu story. Dzieje się tak, ponieważ gdy reguły stosują reguły zabezpieczeń, oceniają zapytanie pod kątem potencjalnego zbioru wyników, a nie rzeczywistych właściwości dokumentów w bazie danych. Jeśli zapytanie potencjalnie może obejmować dokumenty, które naruszają reguły zabezpieczeń, nie powiedzie się.

Z kolei to zapytanie zostanie wykonane, ponieważ zawiera to samo ograniczenie dotyczące pola author co reguły zabezpieczeń:

Prawidłowe: ograniczenia zapytania są zgodne z ograniczeniami reguł zabezpieczeń.

var user = firebase.auth().currentUser;

db.collection("stories").where("author", "==", user.uid).get()

Zabezpieczanie dokumentów i wykonywanie zapytań na ich temat na podstawie pola

Aby lepiej zilustrować interakcję między zapytaniami i regułami, poniższe reguły zabezpieczeń rozszerzają uprawnienia do odczytu kolekcji stories, aby umożliwić każdemu użytkownikowi odczyt dokumentów story, w których pole published ma wartość true.

service cloud.firestore {
  match /databases/{database}/documents {
    match /stories/{storyid} {
      // Anyone can read a published story; only story authors can read unpublished stories
      allow read: if resource.data.published == true || (request.auth != null && request.auth.uid == resource.data.author);
      // Only story authors can write
      allow write: if request.auth != null && request.auth.uid == resource.data.author;
    }
  }
}

Zapytanie dotyczące opublikowanych stron musi zawierać te same ograniczenia co reguły zabezpieczeń:

db.collection("stories").where("published", "==", true).get()

Ograniczenie zapytania .where("published", "==", true) gwarantuje, że resource.data.published to true w przypadku każdego wyniku. Dlatego to zapytanie spełnia reguły zabezpieczeń i może odczytywać dane.

Zapytania: OR

Podczas sprawdzania logicznego zapytania OR (or, in lub array-contains-any) pod kątem zestawu reguł, Reguły sprawdzają każdą wartość porównania osobno. Każda wartość porównania musi spełniać ograniczenia reguły zabezpieczeń. Na przykład w przypadku tej reguły:

match /mydocuments/{doc} {
  allow read: if resource.data.x > 5;
}

Nieprawidłowe: zapytanie nie gwarantuje, że x > 5 we wszystkich potencjalnych dokumentach

// These queries will fail
query(db.collection("mydocuments"),
      or(where("x", "==", 1),
         where("x", "==", 6)
      )
    )

query(db.collection("mydocuments"),
      where("x", "in", [1, 3, 6, 42, 99])
    )

Prawidłowe: zapytanie gwarantuje, że x > 5 dla wszystkich potencjalnych dokumentów.

query(db.collection("mydocuments"),
      or(where("x", "==", 6),
         where("x", "==", 42)
      )
    )

query(db.collection("mydocuments"),
      where("x", "in", [6, 42, 99, 105, 200])
    )

Ocena ograniczeń dotyczących zapytań

Reguły zabezpieczeń mogą też akceptować lub odrzucać zapytania na podstawie ich ograniczeń. Zmienna request.query zawiera właściwości limit, offset i orderBy zapytania. Na przykład reguły zabezpieczeń mogą odrzucać zapytania, które nie ograniczają maksymalnej liczby pobieranych dokumentów do określonego zakresu:

allow list: if request.query.limit <= 10;

Poniższy zestaw reguł pokazuje, jak pisać reguły zabezpieczeń, które oceniają ograniczenia nałożone na zapytania. Ten przykład rozszerza poprzedni zestaw reguł stories o te zmiany:

  • Zbiór reguł rozdziela regułę odczytu na reguły dla get i list.
  • Reguła get ogranicza pobieranie pojedynczych dokumentów do dokumentów publicznych lub dokumentów utworzonych przez użytkownika.
  • Reguła list stosuje te same ograniczenia co get, ale w przypadku zapytań. Sprawdza też limit zapytań, a następnie odrzuca każde zapytanie bez limitu lub z limitem większym niż 10.
  • Zbiór reguł definiuje funkcję authorOrPublished(), aby uniknąć powielania kodu.
service cloud.firestore {

  match /databases/{database}/documents {

    match /stories/{storyid} {

      // Returns `true` if the requested story is 'published'
      // or the user authored the story
      function authorOrPublished() {
        return resource.data.published == true || request.auth.uid == resource.data.author;
      }

      // Deny any query not limited to 10 or fewer documents
      // Anyone can query published stories
      // Authors can query their unpublished stories
      allow list: if request.query.limit <= 10 &&
                     authorOrPublished();

      // Anyone can retrieve a published story
      // Only a story's author can retrieve an unpublished story
      allow get: if authorOrPublished();

      // Only a story's author can write to a story
      allow write: if request.auth.uid == resource.data.author;
    }

  }
}

Zapytania dotyczące grup kolekcji i reguły zabezpieczeń

Domyślnie zapytania są ograniczone do jednej kolekcji i pobierają wyniki tylko z niej. Za pomocą zapytań dotyczących grupy kolekcji możesz pobierać wyniki z grupy kolekcji składającej się ze wszystkich kolekcji o tym samym identyfikatorze. W tej sekcji opisano, jak zabezpieczyć zapytania dotyczące grupy kolekcji za pomocą reguł zabezpieczeń.

Zabezpieczanie dokumentów i wykonywanie zapytań na ich temat na podstawie grup kolekcji

W regułach zabezpieczeń musisz wyraźnie zezwolić na zapytania dotyczące grupy kolekcji, pisząc regułę dla grupy kolekcji:

  1. Upewnij się, że rules_version = '2'; jest pierwszym wierszem zestawu reguł. Zapytania dotyczące grup kolekcji wymagają nowego rekurencyjnego symbolu wieloznacznego {name=**} z reguł zabezpieczeń w wersji 2.
  2. Napisz regułę dla grupy kolekcji za pomocą match /{path=**}/[COLLECTION_ID]/{doc}.

Weźmy na przykład forum zorganizowane w forum dokumenty zawierające posts podkolekcje:

/forums/{forumid}/posts/{postid}

{
  author: "some_auth_id",
  authorname: "some_username",
  content: "I just read a great story.",
}

W tej aplikacji posty mogą być edytowane przez ich właścicieli i odczytywane przez uwierzytelnionych użytkowników:

service cloud.firestore {
  match /databases/{database}/documents {
    match /forums/{forumid}/posts/{post} {
      // Only authenticated users can read
      allow read: if request.auth != null;
      // Only the post author can write
      allow write: if request.auth != null && request.auth.uid == resource.data.author;
    }
  }
}

Każdy uwierzytelniony użytkownik może pobrać posty z dowolnego forum:

db.collection("forums/technology/posts").get()

Co jednak, jeśli chcesz pokazać bieżącemu użytkownikowi jego posty na wszystkich forach? Możesz użyć zapytania dotyczącego grupy kolekcji, aby pobrać wyniki ze wszystkich kolekcji posts:

var user = firebase.auth().currentUser;

db.collectionGroup("posts").where("author", "==", user.uid).get()

W regułach zabezpieczeń musisz zezwolić na to zapytanie, tworząc regułę odczytu lub wyświetlania listy dla grupy kolekcji posts:

rules_version = '2';
service cloud.firestore {

  match /databases/{database}/documents {
    // Authenticated users can query the posts collection group
    // Applies to collection queries, collection group queries, and
    // single document retrievals
    match /{path=**}/posts/{post} {
      allow read: if request.auth != null;
    }
    match /forums/{forumid}/posts/{postid} {
      // Only a post's author can write to a post
      allow write: if request.auth != null && request.auth.uid == resource.data.author;

    }
  }
}

Pamiętaj jednak, że te reguły będą stosowane do wszystkich kolekcji o identyfikatorze posts niezależnie od hierarchii. Te zasady dotyczą na przykład wszystkich tych kolekcji posts:

  • /posts/{postid}
  • /forums/{forumid}/posts/{postid}
  • /forums/{forumid}/subforum/{subforumid}/posts/{postid}

Zabezpieczanie zapytań dotyczących grup kolekcji na podstawie pola

Podobnie jak zapytania dotyczące pojedynczej kolekcji, zapytania dotyczące grupy kolekcji muszą spełniać ograniczenia określone przez reguły zabezpieczeń. Możemy na przykład dodać pole published do każdego posta na forum, tak jak w powyższym przykładzie stories:

/forums/{forumid}/posts/{postid}

{
  author: "some_auth_id",
  authorname: "some_username",
  content: "I just read a great story.",
  published: false
}

Następnie możemy napisać reguły dla grupy kolekcji posts na podstawie stanu published i posta author:

rules_version = '2';
service cloud.firestore {

  match /databases/{database}/documents {

    // Returns `true` if the requested post is 'published'
    // or the user authored the post
    function authorOrPublished() {
      return resource.data.published == true || request.auth.uid == resource.data.author;
    }

    match /{path=**}/posts/{post} {

      // Anyone can query published posts
      // Authors can query their unpublished posts
      allow list: if authorOrPublished();

      // Anyone can retrieve a published post
      // Authors can retrieve an unpublished post
      allow get: if authorOrPublished();
    }

    match /forums/{forumid}/posts/{postid} {
      // Only a post's author can write to a post
      allow write: if request.auth.uid == resource.data.author;
    }
  }
}

Dzięki tym regułom klienci korzystający z przeglądarki, Apple i Androida mogą wysyłać te zapytania:

  • Każdy może pobrać opublikowane posty na forum:

    db.collection("forums/technology/posts").where('published', '==', true).get()
    
  • Każdy może pobrać opublikowane posty autora ze wszystkich forów:

    db.collectionGroup("posts").where("author", "==", "some_auth_id").where('published', '==', true).get()
    
  • Autorzy mogą pobrać wszystkie swoje opublikowane i nieopublikowane posty ze wszystkich forów:

    var user = firebase.auth().currentUser;
    
    db.collectionGroup("posts").where("author", "==", user.uid).get()
    

Zabezpieczanie i wysyłanie zapytań o dokumenty na podstawie grupy kolekcji i ścieżki dokumentu

W niektórych przypadkach możesz ograniczyć zapytania dotyczące grupy kolekcji na podstawie ścieżki dokumentu. Aby utworzyć te ograniczenia, możesz użyć tych samych technik zabezpieczania i wysyłania zapytań o dokumenty na podstawie pola.

Rozważmy aplikację, która śledzi transakcje każdego użytkownika na kilku giełdach akcji i kryptowalut:

/users/{userid}/exchange/{exchangeid}/transactions/{transaction}

{
  amount: 100,
  exchange: 'some_exchange_name',
  timestamp: April 1, 2019 at 12:00:00 PM UTC-7,
  user: "some_auth_id",
}

Zwróć uwagę na pole user. Mimo że wiemy, który użytkownik jest właścicielem dokumentu transaction, na podstawie ścieżki dokumentu, powielamy te informacje w każdym dokumencie transaction, ponieważ pozwala nam to na 2 rzeczy:

  • Zapisuj zapytania dotyczące grupy kolekcji, które są ograniczone do dokumentów zawierających określony element /users/{userid} w ścieżce dokumentu. Przykład:

    var user = firebase.auth().currentUser;
    // Return current user's last five transactions across all exchanges
    db.collectionGroup("transactions").where("user", "==", user).orderBy('timestamp').limit(5)
    
  • Egzekwuj to ograniczenie w przypadku wszystkich zapytań w grupie kolekcji transactions, aby jeden użytkownik nie mógł pobrać dokumentów transaction innego użytkownika.

Egzekwujemy to ograniczenie w naszych regułach zabezpieczeń i uwzględniamy weryfikację danych w polu user:

rules_version = '2';
service cloud.firestore {

  match /databases/{database}/documents {

    match /{path=**}/transactions/{transaction} {
      // Authenticated users can retrieve only their own transactions
      allow read: if resource.data.user == request.auth.uid;
    }

    match /users/{userid}/exchange/{exchangeid}/transactions/{transaction} {
      // Authenticated users can write to their own transactions subcollections
      // Writes must populate the user field with the correct auth id
      allow write: if userid == request.auth.uid && request.data.user == request.auth.uid
    }
  }
}

Dalsze kroki