Ikuti semua informasi yang diumumkan di Firebase Summit, dan pelajari bagaimana Firebase dapat membantu Anda mempercepat pengembangan aplikasi dan menjalankan aplikasi dengan percaya diri. Pelajari Lebih Lanjut

Kueri data dengan aman

Tetap teratur dengan koleksi Simpan dan kategorikan konten berdasarkan preferensi Anda.

Halaman ini dibuat berdasarkan konsep dalam Menyusun Aturan Keamanan dan Menulis Ketentuan untuk Aturan Keamanan untuk menjelaskan bagaimana Aturan Keamanan Cloud Firestore berinteraksi dengan kueri. Perlu melihat lebih dekat bagaimana aturan keamanan memengaruhi kueri yang dapat Anda tulis dan menjelaskan cara memastikan kueri Anda menggunakan batasan yang sama seperti aturan keamanan Anda. Halaman ini juga menjelaskan cara menulis aturan keamanan untuk mengizinkan atau menolak kueri berdasarkan properti kueri seperti limit dan orderBy .

Aturan bukan filter

Saat menulis kueri untuk mengambil dokumen, ingatlah bahwa aturan keamanan bukanlah filter—kueri adalah semua atau tidak sama sekali. Untuk menghemat waktu dan sumber daya Anda, Cloud Firestore mengevaluasi kueri berdasarkan kumpulan hasil potensialnya, bukan nilai bidang sebenarnya untuk semua dokumen Anda. Jika kueri berpotensi mengembalikan dokumen yang klien tidak memiliki izin untuk membaca, seluruh permintaan gagal.

Pertanyaan dan aturan keamanan

Seperti yang ditunjukkan contoh di bawah ini, Anda harus menulis kueri agar sesuai dengan batasan aturan keamanan Anda.

Mengamankan dan menanyakan dokumen berdasarkan auth.uid

Contoh berikut menunjukkan cara menulis kueri untuk mengambil dokumen yang dilindungi oleh aturan keamanan. Pertimbangkan database yang berisi kumpulan dokumen story :

/cerita/{storyid}

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

Selain bidang title dan content , setiap dokumen menyimpan bidang author dan bidang yang published untuk digunakan sebagai kontrol akses. Contoh-contoh ini mengasumsikan aplikasi menggunakan Firebase Authentication untuk menyetel bidang author ke UID pengguna yang membuat dokumen. Firebase Authentication juga mengisi variabel request.auth dalam aturan keamanan.

Aturan keamanan berikut menggunakan variabel request.auth dan resource.data untuk membatasi akses baca dan tulis untuk setiap story kepada penulisnya:

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;
    }
  }
}

Misalkan aplikasi Anda menyertakan halaman yang menunjukkan kepada pengguna daftar dokumen story yang mereka tulis. Anda mungkin berharap dapat menggunakan kueri berikut untuk mengisi halaman ini. Namun, kueri ini akan gagal, karena tidak menyertakan batasan yang sama seperti aturan keamanan Anda:

Tidak valid : Batasan kueri tidak sesuai dengan batasan aturan keamanan

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

Kueri gagal meskipun pengguna saat ini sebenarnya adalah penulis setiap dokumen story . Alasan perilaku ini adalah ketika Cloud Firestore menerapkan aturan keamanan Anda, ia mengevaluasi kueri berdasarkan kumpulan hasil potensialnya , bukan terhadap properti dokumen yang sebenarnya di database Anda. Jika kueri berpotensi menyertakan dokumen yang melanggar aturan keamanan Anda, kueri akan gagal.

Sebaliknya, kueri berikut berhasil, karena menyertakan batasan yang sama pada bidang author sebagai aturan keamanan:

Valid : Batasan kueri sesuai dengan batasan aturan keamanan

var user = firebase.auth().currentUser;

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

Mengamankan dan menanyakan dokumen berdasarkan bidang

Untuk mendemonstrasikan lebih lanjut interaksi antara kueri dan aturan, aturan keamanan di bawah ini memperluas akses baca untuk kumpulan stories untuk memungkinkan setiap pengguna membaca dokumen story di mana bidang yang published disetel ke 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;
    }
  }
}

Kueri untuk halaman yang dipublikasikan harus menyertakan batasan yang sama dengan aturan keamanan:

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

Batasan kueri .where("published", "==", true) menjamin bahwa resource.data.published true untuk hasil apa pun. Oleh karena itu, kueri ini memenuhi aturan keamanan dan diizinkan untuk membaca data.

in dan array-contains-any query

Saat mengevaluasi klausa kueri in atau array-contains-any terhadap set aturan, Cloud Firestore mengevaluasi setiap nilai perbandingan secara terpisah. Setiap nilai perbandingan harus memenuhi batasan aturan keamanan. Misalnya, untuk aturan berikut:

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

Tidak valid : Kueri tidak menjamin bahwa x > 5 untuk semua dokumen potensial

// This query will fail
db.collection("mydocuments").where("x", "in", [1, 3, 6, 42, 99]).get()

Valid : Query menjamin bahwa x > 5 untuk semua dokumen potensial

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

Mengevaluasi batasan pada kueri

Aturan keamanan Anda juga dapat menerima atau menolak kueri berdasarkan batasannya. Variabel request.query berisi properti limit , offset , dan orderBy dari sebuah kueri. Misalnya, aturan keamanan Anda dapat menolak kueri apa pun yang tidak membatasi jumlah maksimum dokumen yang diambil ke rentang tertentu:

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

Aturan berikut menunjukkan cara menulis aturan keamanan yang mengevaluasi batasan yang ditempatkan pada kueri. Contoh ini memperluas kumpulan aturan stories sebelumnya dengan perubahan berikut:

  • Ruleset memisahkan aturan baca menjadi aturan untuk get dan list .
  • Aturan get membatasi pengambilan dokumen tunggal ke dokumen publik atau dokumen yang ditulis pengguna.
  • Aturan list menerapkan batasan yang sama seperti get tetapi untuk kueri. Itu juga memeriksa batas kueri, lalu menolak kueri apa pun tanpa batas atau dengan batas lebih besar dari 10.
  • Aturan ini mendefinisikan fungsi authorOrPublished() untuk menghindari duplikasi kode.
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;
    }

  }
}

Kueri grup koleksi dan aturan keamanan

Secara default, kueri dicakupkan ke satu koleksi dan hanya mengambil hasil dari koleksi itu. Dengan kueri grup koleksi , Anda dapat mengambil hasil dari grup koleksi yang terdiri dari semua koleksi dengan ID yang sama. Bagian ini menjelaskan cara mengamankan kueri grup koleksi Anda menggunakan aturan keamanan.

Mengamankan dan menanyakan dokumen berdasarkan grup koleksi

Dalam aturan keamanan Anda, Anda harus secara eksplisit mengizinkan kueri grup koleksi dengan menulis aturan untuk grup koleksi:

  1. Pastikan rules_version = '2'; adalah baris pertama dari aturan Anda. Kueri grup koleksi memerlukan perilaku karakter pengganti rekursif {name=**} baru dari aturan keamanan versi 2.
  2. Tulis aturan untuk grup koleksi Anda menggunakan match /{path=**}/ [COLLECTION_ID] /{doc} .

Misalnya, pertimbangkan forum yang diatur ke dalam dokumen forum yang berisi subkoleksi posts :

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

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

Dalam aplikasi ini, kami membuat postingan dapat diedit oleh pemiliknya dan dibaca oleh pengguna yang diautentikasi:

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;
    }
  }
}

Setiap pengguna yang diautentikasi dapat mengambil kiriman dari forum tunggal mana pun:

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

Tetapi bagaimana jika Anda ingin menunjukkan kepada pengguna saat ini posting mereka di semua forum? Anda dapat menggunakan kueri grup koleksi untuk mengambil hasil dari semua koleksi posts :

var user = firebase.auth().currentUser;

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

Dalam aturan keamanan Anda, Anda harus mengizinkan kueri ini dengan menulis aturan baca atau daftar untuk grup koleksi 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;

    }
  }
}

Namun, perhatikan bahwa aturan ini akan berlaku untuk semua koleksi dengan posts ID , terlepas dari hierarkinya. Misalnya, aturan ini berlaku untuk semua koleksi posts berikut:

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

Mengamankan kueri grup koleksi berdasarkan bidang

Seperti kueri koleksi tunggal, kueri grup koleksi juga harus memenuhi batasan yang ditetapkan oleh aturan keamanan Anda. Misalnya, kami dapat menambahkan bidang yang published ke setiap posting forum seperti yang kami lakukan pada contoh stories di atas:

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

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

Kami kemudian dapat menulis aturan untuk grup kumpulan posts berdasarkan status yang published dan author posting:

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;
    }
  }
}

Dengan aturan ini, klien Web, Apple, dan Android dapat membuat kueri berikut:

  • Siapa pun dapat mengambil postingan yang dipublikasikan di forum:

    db.collection("forums/technology/posts").where('published', '==', true).get()
    
  • Siapa pun dapat mengambil postingan penulis yang dipublikasikan di semua forum:

    db.collectionGroup("posts").where("author", "==", "some_auth_id").where('published', '==', true).get()
    
  • Penulis dapat mengambil semua posting mereka yang diterbitkan dan tidak diterbitkan di semua forum:

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

Mengamankan dan menanyakan dokumen berdasarkan grup koleksi dan jalur dokumen

Dalam beberapa kasus, Anda mungkin ingin membatasi kueri grup koleksi berdasarkan jalur dokumen. Untuk membuat batasan ini, Anda bisa menggunakan teknik yang sama untuk mengamankan dan membuat kueri dokumen berdasarkan bidang.

Pertimbangkan aplikasi yang melacak setiap transaksi pengguna di antara beberapa bursa saham dan mata uang kripto:

/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",
}

Perhatikan bidang user . Meskipun kami mengetahui pengguna mana yang memiliki dokumen transaction dari jalur dokumen, kami menduplikasi informasi ini di setiap dokumen transaction karena memungkinkan kami melakukan dua hal:

  • Tulis kueri grup koleksi yang dibatasi untuk dokumen yang menyertakan /users/{userid} tertentu di jalur dokumen mereka. Sebagai contoh:

    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)
    
  • Terapkan pembatasan ini untuk semua kueri pada grup pengumpulan transactions sehingga satu pengguna tidak dapat mengambil dokumen transaction pengguna lain.

Kami menerapkan pembatasan ini dalam aturan keamanan kami dan menyertakan validasi data untuk bidang 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
    }
  }
}

Langkah selanjutnya