Aturan Keamanan Firebase Realtime Database memungkinkan Anda mengontrol akses ke data yang disimpan di database Anda. Sintaks aturan fleksibel memungkinkan Anda membuat aturan yang cocok dengan apa pun, dari semua penulisan ke database Anda hingga operasi pada node individual.
Aturan Keamanan Realtime Database adalah konfigurasi deklaratif untuk database Anda. Ini berarti bahwa aturan ditentukan secara terpisah dari logika produk. Ini memiliki sejumlah keuntungan: klien tidak bertanggung jawab untuk menegakkan keamanan, implementasi buggy tidak akan membahayakan data Anda, dan mungkin yang terpenting, tidak perlu wasit perantara, seperti server, untuk melindungi data dari dunia.
Topik ini menjelaskan sintaks dasar dan struktur Aturan Keamanan Realtime Database yang digunakan untuk membuat kumpulan aturan lengkap.
Menyusun Aturan Keamanan Anda
Aturan Keamanan Realtime Database terdiri dari ekspresi seperti JavaScript yang terdapat dalam dokumen JSON. Struktur aturan Anda harus mengikuti struktur data yang telah Anda simpan di database Anda.
Aturan dasar mengidentifikasi sekumpulan node yang akan diamankan, metode akses (misalnya, membaca, menulis) yang terlibat, dan kondisi di mana akses diizinkan atau ditolak. Dalam contoh berikut, kondisi kita akan menjadi pernyataan true
dan false
sederhana, tetapi di topik berikutnya kita akan membahas cara yang lebih dinamis untuk mengekspresikan kondisi.
Jadi, misalnya, jika kita mencoba mengamankan child_node
bawah parent_node
, sintaks umum yang harus diikuti adalah:
{ "rules": { "parent_node": { "child_node": { ".read": <condition>, ".write": <condition>, ".validate": <condition>, } } } }
Mari terapkan pola ini. Misalnya, Anda melacak daftar pesan dan memiliki data yang terlihat seperti ini:
{ "messages": { "message0": { "content": "Hello", "timestamp": 1405704370369 }, "message1": { "content": "Goodbye", "timestamp": 1405704395231 }, ... } }
Aturan Anda harus disusun dengan cara yang sama. Berikut sekumpulan aturan untuk keamanan hanya baca yang mungkin masuk akal untuk struktur data ini. Contoh ini mengilustrasikan bagaimana kita menentukan node database tempat aturan berlaku dan kondisi untuk mengevaluasi aturan di node tersebut.
{ "rules": { // For requests to access the 'messages' node... "messages": { // ...and the individual wildcarded 'message' nodes beneath // (we'll cover wildcarding variables more a bit later).... "$message": { // For each message, allow a read operation if <condition>. In this // case, we specify our condition as "true", so read access is always granted. ".read": "true", // For read-only behavior, we specify that for write operations, our // condition is false. ".write": "false" } } } }
Operasi Aturan Dasar
Ada tiga jenis aturan untuk menegakkan keamanan berdasarkan jenis operasi yang dilakukan pada data: .write
, .read
, dan .validate
. Berikut ringkasan singkat dari tujuan mereka:
Jenis Aturan | |
---|---|
.Baca | Menjelaskan jika dan kapan data diizinkan untuk dibaca oleh pengguna. |
.menulis | Menjelaskan jika dan kapan data diperbolehkan untuk ditulis. |
.mengesahkan | Mendefinisikan seperti apa nilai yang diformat dengan benar, apakah itu memiliki atribut turunan, dan tipe datanya. |
Variabel Tangkapan Karakter Pengganti
Semua pernyataan aturan mengarah ke node. Pernyataan dapat mengarah ke node tertentu atau menggunakan variabel capture $
wildcard untuk mengarahkan ke kumpulan node pada tingkat hierarki. Gunakan variabel tangkap ini untuk menyimpan nilai kunci node untuk digunakan di dalam pernyataan aturan berikutnya. Teknik ini memungkinkan Anda menulis ketentuan Aturan yang lebih kompleks, sesuatu yang akan kita bahas lebih detail di topik berikutnya.
{ "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')" } } } } }
Variabel $
dinamis juga dapat digunakan secara paralel dengan nama jalur konstan. Dalam contoh ini, kami menggunakan variabel $other
untuk mendeklarasikan aturan .validate
yang memastikan bahwa widget
tidak memiliki .validate
selain title
dan color
. Tulisan apa pun yang akan menghasilkan anak tambahan yang dibuat akan gagal.
{ "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 } } } }
Aturan Baca dan Tulis Bertingkat
.read
dan .write
aturan bekerja dari atas ke bawah, dengan aturan dangkal override aturan yang lebih dalam. Jika aturan memberikan izin baca atau tulis pada jalur tertentu, aturan tersebut juga memberikan akses ke semua node turunan di bawahnya. Pertimbangkan struktur berikut:
{ "rules": { "foo": { // allows read to /foo/* ".read": "data.child('baz').val() === true", "bar": { /* ignored, since read was allowed already */ ".read": false } } } }
Struktur keamanan ini memungkinkan /bar/
untuk dibaca setiap kali /foo/
berisi anak baz
dengan nilai true
. Aturan ".read": false
bawah /foo/bar/
tidak berpengaruh di sini, karena akses tidak dapat dicabut oleh jalur turunan.
Meskipun mungkin tidak tampak intuitif, ini adalah bagian yang kuat dari bahasa aturan dan memungkinkan hak akses yang sangat kompleks untuk diterapkan dengan sedikit usaha. Ini akan diilustrasikan saat kita masuk ke keamanan berbasis pengguna nanti dalam panduan ini.
Perhatikan bahwa aturan .validate
tidak .validate
. Semua aturan validasi harus dipenuhi di semua tingkat hierarki agar penulisan diizinkan.
Aturan Bukanlah Filter
Aturan diterapkan secara atomik. Artinya, operasi baca atau tulis langsung gagal jika tidak ada aturan di lokasi tersebut atau di lokasi induk yang memberikan akses. Meskipun setiap jalur turunan yang terpengaruh dapat diakses, membaca di lokasi induk akan gagal sepenuhnya. Pertimbangkan struktur ini:
{ "rules": { "records": { "rec1": { ".read": true }, "rec2": { ".read": false } } } }
Tanpa memahami bahwa aturan dievaluasi secara atomik, tampaknya mengambil /records/
path akan mengembalikan rec1
tetapi tidak rec2
. Namun, hasil sebenarnya adalah kesalahan:
JavaScript
var db = firebase.database(); db.ref("records").once("value", function(snap) { // success method is not called }, function(err) { // error callback triggered with PERMISSION_DENIED });
Objective-C
FIRDatabaseReference *ref = [[FIRDatabase database] reference]; [[_ref child:@"records"] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) { // success block is not called } withCancelBlock:^(NSError * _Nonnull error) { // cancel block triggered with PERMISSION_DENIED }];
Cepat
var ref = FIRDatabase.database().reference() ref.child("records").observeSingleEventOfType(.Value, withBlock: { snapshot in // success block is not called }, withCancelBlock: { error in // cancel block triggered with PERMISSION_DENIED })
Jawa
FirebaseDatabase database = FirebaseDatabase.getInstance(); DatabaseReference ref = database.getReference("records"); ref.addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot snapshot) { // success method is not called } @Override public void onCancelled(FirebaseError firebaseError) { // error callback triggered with PERMISSION_DENIED }); });
BERISTIRAHAT
curl https://docs-examples.firebaseio.com/rest/records/ # response returns a PERMISSION_DENIED error
Karena operasi baca di /records/
bersifat atomic, dan tidak ada aturan baca yang memberikan akses ke semua data di bawah /records/
, ini akan memunculkan kesalahan PERMISSION_DENIED
. Jika kita mengevaluasi aturan ini di simulator keamanan di Firebase console , kita dapat melihat bahwa operasi baca ditolak karena tidak ada aturan baca yang mengizinkan akses ke /records/
path. Namun, perhatikan bahwa aturan untuk rec1
tidak pernah dievaluasi karena tidak ada di jalur yang kami minta. Untuk mengambil rec1
, kita perlu mengaksesnya secara langsung:
JavaScript
var db = firebase.database(); db.ref("records/rec1").once("value", function(snap) { // SUCCESS! }, function(err) { // error callback is not called });
Objective-C
FIRDatabaseReference *ref = [[FIRDatabase database] reference]; [[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) { // SUCCESS! }];
Cepat
var ref = FIRDatabase.database().reference() ref.child("records/rec1").observeSingleEventOfType(.Value, withBlock: { snapshot in // SUCCESS! })
Jawa
FirebaseDatabase database = FirebaseDatabase.getInstance(); DatabaseReference ref = database.getReference("records/rec1"); ref.addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot snapshot) { // SUCCESS! } @Override public void onCancelled(FirebaseError firebaseError) { // error callback is not called } });
BERISTIRAHAT
curl https://docs-examples.firebaseio.com/rest/records/rec1 # SUCCESS!
Pernyataan Tumpang tindih
Ada kemungkinan lebih dari satu aturan untuk diterapkan ke node. Dalam kasus di mana beberapa ekspresi aturan mengidentifikasi sebuah node, metode akses ditolak jika salah satu kondisinya false
:
{ "rules": { "messages": { // A rule expression that applies to all nodes in the 'messages' node "$message": { ".read": "true", ".write": "true" }, // A second rule expression applying specifically to the 'message1` node "message1": { ".read": "false", ".write": "false" } } } }
Pada contoh di atas, pembacaan ke node message1
akan ditolak karena aturan kedua selalu false
, meskipun aturan pertama selalu true
.
Langkah selanjutnya
Anda dapat memperdalam pemahaman Anda tentang Aturan Keamanan Firebase Realtime Database:
Pelajari konsep utama berikutnya dari bahasa Aturan, kondisi dinamis, yang memungkinkan Aturan Anda memeriksa otorisasi pengguna, membandingkan data yang ada dan yang masuk, memvalidasi data yang masuk, memeriksa struktur kueri yang berasal dari klien, dan banyak lagi.
Tinjau kasus penggunaan keamanan umum dan definisi Aturan Keamanan Firebase yang mengatasinya .