Mempelajari sintaksis inti bahasa Aturan Keamanan Realtime Database

Dengan Aturan Keamanan Firebase Realtime Database, Anda dapat mengontrol akses ke data yang disimpan dalam database Anda. Dengan sintaksis aturan yang fleksibel, Anda dapat 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. Artinya, penetapan aturan terpisah dari logika produk. Hal ini memiliki sejumlah keuntungan: klien tidak bertanggung jawab untuk menerapkan keamanan, implementasi yang berisi bug tidak akan merusak data Anda, dan mungkin yang paling penting, tidak perlu perantara, seperti server, untuk melindungi data dari dunia luar.

Topik ini menjelaskan sintaksis dasar dan struktur yang digunakan Aturan Keamanan Realtime Database untuk membuat kumpulan aturan lengkap.

Membuat Struktur Aturan Keamanan

Aturan Keamanan Realtime Database terdiri dari ekspresi serupa JavaScript yang terdapat dalam dokumen JSON. Struktur aturan Anda harus mengikuti struktur data yang telah disimpan di database.

Aturan dasar mengidentifikasi kumpulan node yang akan diamankan, metode akses (misalnya, baca, tulis) yang terlibat, dan kondisi yang mendasari diizinkan atau ditolaknya akses. Dalam contoh berikut, kondisi yang digunakan akan berupa pernyataan true dan false yang sederhana, tetapi pada topik berikutnya kita akan membahas cara yang lebih dinamis untuk mengekspresikan kondisi.

Jadi, misalnya, jika kita mencoba mengamankan child_node dengan parent_node, sintaksis umum yang harus diikuti adalah:

{
  "rules": {
    "parent_node": {
      "child_node": {
        ".read": <condition>,
        ".write": <condition>,
        ".validate": <condition>,
      }
    }
  }
}

Mari terapkan pola ini. Misalnya, Anda ingin melacak daftar pesan dan data yang dimiliki terlihat seperti ini:

{
  "messages": {
    "message0": {
      "content": "Hello",
      "timestamp": 1405704370369
    },
    "message1": {
      "content": "Goodbye",
      "timestamp": 1405704395231
    },
    ...
  }
}

Aturan Anda harus disusun dengan cara yang sama. Berikut adalah kumpulan aturan untuk keamanan hanya-baca yang mungkin cocok untuk struktur data ini. Contoh ini menggambarkan cara kami menentukan node database yang dikenai penerapan aturan dan kondisi untuk mengevaluasi aturan pada 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 menerapkan keamanan berdasarkan jenis operasi yang dilakukan pada data: .write, .read, dan .validate. Berikut ringkasan kegunaannya:

Jenis Aturan
.read Menjelaskan apakah dan kapan data boleh dibaca oleh pengguna.
.write Menjelaskan apakah dan kapan data boleh ditulis.
.validate Menentukan tampilan nilai yang diformat dengan benar, baik nilai tersebut memiliki atribut turunan maupun tidak, dan jenis data.

Variabel Tangkapan Karakter Pengganti

Semua pernyataan aturan mengarah ke node. Pernyataan dapat mengarah ke node tertentu atau menggunakan variabel tangkapan karakter pengganti $ untuk mengarahkan ke kumpulan node pada level hierarki. Gunakan variabel tangkapan ini untuk menyimpan nilai kunci node untuk digunakan dalam pernyataan aturan berikutnya. Teknik ini memungkinkan Anda menulis kondisi Aturan yang lebih rumit, yakni hal yang akan kita bahas secara lebih mendetail dalam 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 yang tetap. Dalam contoh ini, kita menggunakan variabel $other untuk mendeklarasikan aturan .validate yang memastikan bahwa widget tidak memiliki turunan selain title dan color. Setiap operasi tulis yang menyebabkan terciptanya turunan tambahan 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 yang Bersifat Menurun

Aturan .read dan .write berfungsi dari atas ke bawah. Aturan yang lebih dangkal akan menggantikan aturan yang lebih dalam. Jika aturan memberikan izin baca atau tulis pada jalur tertentu, maka aturan tersebut juga akan 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 pembacaan /bar/ jika /foo/ berisi turunan baz dengan nilai true. Aturan ".read": false pada bagian /foo/bar/ tidak berpengaruh di sini, karena akses tidak dapat dicabut oleh jalur turunan.

Walaupun mungkin kelihatannya tidak intuitif, ini adalah bagian yang andal dari bahasa aturan, sehingga hak istimewa akses yang sangat kompleks dapat diimplementasikan dengan usaha minimal. Gambarannya akan dijelaskan saat kita sampai ke topik keamanan berbasis pengguna pada bagian selanjutnya dalam panduan ini.

Perlu diperhatikan bahwa aturan .validate tidak bersifat menurun. Semua aturan validasi harus terpenuhi di semua tingkat hierarki agar operasi tulis diizinkan.

Aturan Bukanlah Filter

Aturan diterapkan secara atomis. Artinya, operasi baca atau tulis akan langsung gagal jika tidak ada aturan di lokasi tersebut atau di lokasi induk yang memberikan akses. Sekalipun setiap jalur turunan yang terpengaruh bisa diakses, operasi baca di lokasi induk tidak akan dapat dilakukan. Perhatikan struktur ini:

{
  "rules": {
    "records": {
      "rec1": {
        ".read": true
      },
      "rec2": {
        ".read": false
      }
    }
  }
}

Tanpa memahami bahwa aturan dievaluasi secara atomis, mengambil jalur /records/ mungkin seolah akan menampilkan rec1 dan bukan rec2. Namun, hasil sesungguhnya adalah error:

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
Catatan: Produk Firebase ini tidak tersedia di target App Clip.
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
}];
Swift
Catatan: Produk Firebase ini tidak tersedia di target App Clip.
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
})
Java
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
  });
});
REST
curl https://docs-examples.firebaseio.com/rest/records/
# response returns a PERMISSION_DENIED error

Karena operasi baca di /records/ bersifat atomis, dan tidak ada aturan baca yang memberikan akses ke semua data pada bagian /records/, tindakan ini akan menghasilkan error PERMISSION_DENIED. Jika kita mengevaluasi aturan ini dalam simulator keamanan di Firebase console, kita dapat melihat bahwa operasi baca ditolak karena tidak ada aturan baca yang mengizinkan akses ke jalur /records/. 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
Catatan: Produk Firebase ini tidak tersedia di target App Clip.
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
[[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
    // SUCCESS!
}];
Swift
Catatan: Produk Firebase ini tidak tersedia di target App Clip.
var ref = FIRDatabase.database().reference()
ref.child("records/rec1").observeSingleEventOfType(.Value, withBlock: { snapshot in
    // SUCCESS!
})
Java
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
  }
});
REST
curl https://docs-examples.firebaseio.com/rest/records/rec1
# SUCCESS!

Pernyataan yang Tumpang Tindih

Lebih dari satu aturan mungkin diterapkan untuk satu node. Jika beberapa ekspresi aturan mengidentifikasi satu node, metode akses akan ditolak jika salah satu kondisinya adalah 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, operasi baca ke node message1 akan ditolak karena aturan kedua selalu false, meskipun aturan pertama selalu true.

Langkah berikutnya

Anda dapat memperdalam pemahaman 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 masuk, memeriksa struktur kueri yang berasal dari klien, serta lainnya.

  • Tinjau kasus penggunaan keamanan umum dan definisi Aturan Keamanan Firebase yang menanganinya.