Membuat kueri data dengan aman
Halaman ini menjabarkan konsep dalam Membuat Struktur Aturan Keamanan dan Menulis Kondisi untuk Aturan Keamanan untuk menjelaskan cara interaksi Aturan Keamanan Firestore dengan kueri. Di sini Anda dapat menemukan bahasan lebih dalam tentang pengaruh aturan keamanan terhadap kueri yang dapat Anda tulis, dan menjelaskan cara memastikan kueri Anda menggunakan batasan yang sama seperti aturan keamanan. Halaman ini juga menjelaskan cara menulis aturan keamanan untuk mengizinkan atau menolak kueri berdasarkan properti kueri seperti limit
dan orderBy
.
Aturan bukanlah filter
Saat menulis kueri untuk mengambil dokumen, ingat bahwa aturan keamanan bukanlah filter. Kueri bersifat semua atau tidak ada sama sekali. Untuk menghemat waktu dan resource, Firestore mengevaluasi kueri berdasarkan kumpulan hasil potensialnya, bukan nilai kolom sebenarnya untuk semua dokumen Anda. Jika kueri berpotensi untuk menampilkan dokumen, tapi klien tidak memiliki akses baca ke dokumen tersebut, keseluruhan permintaan akan gagal.
Kueri dan aturan keamanan
Seperti yang ditunjukkan contoh di bawah ini, Anda harus menuliskan kueri agar sesuai dengan batasan aturan keamanan.
Mengamankan dan membuat kueri 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
:
/stories/{storyid}
{
title: "A Great Story",
content: "Once upon a time...",
author: "some_auth_id",
published: false
}
Selain kolom title
dan content
, setiap dokumen menyimpan kolom author
dan published
yang digunakan untuk kontrol akses. Contoh ini mengasumsikan bahwa aplikasi tersebut menggunakan Firebase Authentication untuk menetapkan kolom 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 cocok dengan batasan aturan keamanan
// This query will fail
db.collection("stories").get()
Kueri gagal meskipun pengguna saat ini adalah penulis setiap dokumen story
. Perilaku ini muncul karena ketika menerapkan aturan keamanan Anda, Firestore akan mengevaluasi kueri berdasarkan kumpulan hasil potensial, bukan berdasarkan properti dokumen sebenarnya di database Anda. Jika kueri berpotensi menyertakan dokumen yang melanggar aturan keamanan Anda, kueri akan gagal.
Sebaliknya, kueri berikut berhasil karena menyertakan batasan di kolom author
yang sama dengan batasan pada 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 membuat kueri dokumen berdasarkan kolom
Untuk menunjukkan lebih lanjut tentang interaksi antara kueri dan aturan, aturan keamanan di bawah ini memperluas akses baca untuk koleksi stories
agar pengguna dapat membaca dokumen story
tempat kolom 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 seperti aturan keamanan:
db.collection("stories").where("published", "==", true).get()
Batasan kueri .where("published", "==", true)
menjamin bahwa resource.data.published
adalah true
untuk setiap hasil. Oleh karena itu, kueri ini memenuhi aturan keamanan dan diizinkan untuk membaca data.
OR
kueri
Saat mengevaluasi kueri OR
yang logis (or
, in
, atau array-contains-any
) berdasarkan kumpulan aturan, 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
// 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])
)
Valid: Kueri menjamin bahwa x > 5
untuk semua dokumen potensial
query(db.collection("mydocuments"),
or(where("x", "==", 6),
where("x", "==", 42)
)
)
query(db.collection("mydocuments"),
where("x", "in", [6, 42, 99, 105, 200])
)
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 suatu kueri. Misalnya, aturan keamanan Anda dapat menolak kueri apa saja yang tidak membatasi jumlah maksimum dokumen yang diambil pada rentang tertentu:
allow list: if request.query.limit <= 10;
Kumpulan aturan berikut menunjukkan cara menulis aturan keamanan yang mengevaluasi batasan yang digunakan pada kueri. Contoh ini memperluas kumpulan aturan stories
sebelumnya dengan perubahan berikut:
- Kumpulan aturan tersebut memisahkan aturan operasi baca menjadi aturan
get
danlist
. - Aturan
get
membatasi pengambilan dokumen tunggal untuk dokumen publik atau dokumen yang ditulis oleh pengguna. - Aturan
list
menerapkan pembatasan yang sama sepertiget
tetapi untuk kueri. Aturan ini juga memeriksa batas kueri, lalu menolak kueri yang tidak memiliki batas atau yang batasnya lebih dari 10. - Kumpulan aturan menentukan fungsi
authorOrPublished()
demi 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 dibatasi untuk satu koleksi dan kueri mengambil hasil hanya dari koleksi tersebut. Dengan kueri grup koleksi, Anda dapat mengambil hasil dari grup koleksi yang terdiri atas semua koleksi dengan ID yang sama. Bagian ini menjelaskan cara mengamankan kueri grup koleksi menggunakan aturan keamanan.
Mengamankan dan membuat kueri dokumen berdasarkan grup koleksi
Dalam aturan keamanan, Anda harus secara eksplisit mengizinkan kueri grup koleksi dengan menulis aturan untuk grup koleksi:
- Pastikan
rules_version = '2';
adalah baris pertama dari kumpulan aturan Anda. Kueri grup koleksi memerlukan perilaku karakter pengganti rekursif baru{name=**}
dari aturan keamanan versi 2. - Tulis aturan untuk grup koleksi Anda menggunakan
match /{path=**}/[COLLECTION_ID]/{doc}
.
Misalnya, sebaiknya susun forum menjadi 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 yang dapat diedit oleh pemiliknya dan dapat dibaca oleh pengguna terautentikasi:
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 terautentikasi dapat mengambil postingan dari setiap forum tunggal:
db.collection("forums/technology/posts").get()
Tetapi bagaimana jika Anda ingin menampilkan postingan kepada pengguna saat ini 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 harus mengizinkan kueri ini dengan menulis aturan baca atau list 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, perlu diingat bahwa aturan ini akan berlaku untuk semua koleksi dengan posts
ID, terlepas dari hierarki. 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 kolom
Seperti kueri koleksi tunggal, kueri grup koleksi juga harus memenuhi batasan yang ditetapkan oleh aturan keamanan Anda. Misalnya, kita dapat menambahkan kolom published
ke setiap postingan di forum seperti yang kita 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
}
Kemudian, kita dapat menulis aturan untuk grup koleksi posts
berdasarkan status published
dan author
postingan:
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 saja dapat mengambil postingan yang dipublikasikan di forum:
db.collection("forums/technology/posts").where('published', '==', true).get()
Siapa saja dapat mengambil postingan yang dipublikasikan penulis di semua forum:
db.collectionGroup("posts").where("author", "==", "some_auth_id").where('published', '==', true).get()
Penulis dapat mengambil semua postingan mereka yang dipublikasikan dan tidak dipublikasikan di semua forum:
var user = firebase.auth().currentUser; db.collectionGroup("posts").where("author", "==", user.uid).get()
Mengamankan dan membuat kueri dokumen berdasarkan grup koleksi dan lokasi dokumen
Dalam beberapa kasus, Anda mungkin ingin membatasi kueri grup koleksi berdasarkan lokasi dokumen. Untuk membuat batasan ini, Anda bisa menggunakan teknik yang sama yang digunakan untuk mengamankan dan membuat kueri dokumen berdasarkan kolom.
Pertimbangkan aplikasi yang melacak transaksi setiap pengguna di 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 kolom user
. Meskipun kami tahu pengguna mana yang memiliki dokumen transaction
dari jalur dokumen, kami menduplikasi informasi ini di setiap dokumen transaction
. Dengan begitu, kami dapat melakukan dua hal berikut:
Menulis kueri grup koleksi yang dibatasi untuk dokumen yang menyertakan
/users/{userid}
spesifik di jalur dokumen. 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)
Menerapkan pembatasan ini untuk semua kueri di grup koleksi
transactions
sehingga setiap pengguna tidak dapat mengambil dokumentransaction
milik pengguna lain.
Kami menerapkan pembatasan ini dalam aturan keamanan kami dan menyertakan validasi data untuk kolom 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 berikutnya
- Untuk mengetahui contoh yang lebih detail tentang kontrol akses berbasis peran, lihat Mengamankan Akses Data untuk Pengguna dan Grup.
- Baca referensi aturan keamanan.