Cara Penerapan Aturan Keamanan

Keamanan dapat menjadi salah satu bagian paling kompleks dari proses pengembangan aplikasi. Pada sebagian besar aplikasi, developer harus membangun dan menjalankan server yang menangani autentikasi (siapa penggunanya) dan otorisasi (apa yang bisa dilakukan pengguna).

Aturan Keamanan Firebase menghapus lapisan tengah (server) dan memungkinkan Anda menentukan izin berbasis path untuk klien yang terhubung ke data Anda secara langsung. Gunakan panduan ini untuk mempelajari lebih lanjut tentang cara penerapan aturan pada permintaan yang masuk.

Pilih produk untuk mempelajari lebih lanjut tentang aturannya.

Cloud Firestore

Struktur dasar

Aturan Keamanan Firebase di Cloud Firestore dan Cloud Storage menggunakan sintaks berikut:

service <<name>> {
  // Match the resource path.
  match <<path>> {
    // Allow the request if the following conditions are true.
    allow <<methods>> : if <<condition>>
  }
}

Berikut konsep utama yang perlu dipahami saat membuat aturan:

  • Permintaan: Metode yang diaktifkan pada pernyataan allow. Metode tersebut adalah metode yang boleh dijalankan. Metode standarnya adalah: get, list, create, update, dan delete. Metode mudah read dan write akan mengaktifkan akses baca dan tulis pada database atau path penyimpanan yang ditentukan.
  • Path: Lokasi database atau penyimpanan, direpresentasikan sebagai path URI.
  • Aturan: Pernyataan allow, berisi kondisi yang akan mengizinkan permintaan jika permintaan tersebut dinilai benar.

Mencocokkan path

Semua pernyataan kecocokan harus mengarah ke dokumen, bukan koleksi. Pernyataan kecocokan dapat mengarah ke dokumen tertentu, seperti pada match /cities/SF atau menggunakan karakter pengganti untuk mengarah ke dokumen pada lokasi tertentu, seperti pada match /cities/{city}.

Pada contoh di atas, pernyataan kecocokan menggunakan sintaks karakter pengganti {city}. Hal ini berarti aturan tersebut berlaku untuk setiap dokumen pada koleksi cities, seperti /cities/SF atau /cities/NYC. Jika ekspresi allow pada pernyataan kecocokan dievaluasi, variabel city akan menentukan nama dokumen kota, misalnya SF atau NYC.

Mencocokkan subkoleksi

Data di Cloud Firestore disusun menjadi koleksi dokumen, dan setiap dokumen dapat memperluas hierarkinya hingga subkoleksi. Anda perlu memahami bagaimana aturan keamanan berinteraksi dengan data hierarkis.

Perhatikan situasi di mana setiap dokumen dalam koleksi cities berisi subkoleksi landmarks. Aturan keamanan hanya berlaku pada lokasi yang cocok, sehingga kontrol akses yang ditetapkan pada koleksi cities tidak berlaku untuk subkoleksi landmarks. Sebagai gantinya, tulis aturan eksplisit untuk mengontrol akses ke subkoleksi:

service cloud.firestore {
  match /databases/{database}/documents {
    match /cities/{city} {
      allow read, write: if <condition>;

        // Explicitly define rules for the 'landmarks' subcollection
        match /landmarks/{landmark} {
          allow read, write: if <condition>;
        }
    }
  }
}

Jika pernyataan match dibuat bertingkat, lokasi pernyataan match dalam akan selalu bergantung pada lokasi pernyataan match luar. Oleh karena itu, kumpulan aturan berikut adalah setara:

service cloud.firestore {
  match /databases/{database}/documents {
    match /cities/{city} {
      match /landmarks/{landmark} {
        allow read, write: if <condition>;
      }
    }
  }
}
service cloud.firestore {
  match /databases/{database}/documents {
    match /cities/{city}/landmarks/{landmark} {
      allow read, write: if <condition>;
    }
  }
}

Jika Anda ingin menerapkan aturan ke sebarang hierarki dalam, gunakan sintaks karakter pengganti rekursif, {name=**}:

service cloud.firestore {
  match /databases/{database}/documents {
    // Matches any document in the cities collection as well as any document
    // in a subcollection.
    match /cities/{document=**} {
      allow read, write: if <condition>;
    }
  }
}

Saat menggunakan sintaks karakter pengganti rekursif, variabel karakter pengganti akan memuat seluruh segmen jalur yang cocok, meskipun dokumen tersebut berada di subkoleksi bertingkat yang dalam. Misalnya, aturan yang tercantum di atas akan cocok dengan dokumen yang berada di /cities/SF/landmarks/coit_tower, dan nilai variabel document adalah SF/landmarks/coit_tower.

Karakter pengganti rekursif tidak dapat mencocokkan lokasi kosong, sehingga match /cities/{city}/{document=**} akan mencocokkan dokumen di subkoleksi tetapi bukan dokumen di koleksi cities, sedangkan match /cities/{document=**} akan mencocokkan dokumen baik yang berada di koleksi maupun subkoleksi cities.

Ada kemungkinan dokumen cocok dengan lebih dari 1 pernyataan match. Jika beberapa ekspresi allow cocok dengan sebuah permintaan, akses akan diizinkan jika salah satu kondisi ini true:

service cloud.firestore {
  match /databases/{database}/documents {
    // Matches any document in the 'cities' collection.
    match /cities/{city} {
      allow read, write: if false;
    }

    // Matches any document in the 'cities' collection or subcollections.
    match /cities/{document=**} {
      allow read, write: if true;
    }
  }
}

Pada contoh di atas, semua pembacaan dan penulisan ke koleksi cities akan diizinkan karena aturan keduanya selalu true, meskipun aturan pertamanya selalu false.

Batas aturan keamanan

Perhatikan batas berikut saat menangani aturan keamanan:

Batas Detail
Jumlah maksimum panggilan exists(), get(), dan getAfter() per permintaan
  • 10 untuk permintaan kueri dokumen tunggal.
  • 20 untuk pembacaan, transaksi, dan penulisan batch multi-dokumen. Batas 10 sebelumnya juga berlaku untuk setiap operasi.

    Misalnya, bayangkan Anda membuat permintaan penulisan batch dengan 3 operasi penulisan dan aturan keamanan yang menggunakan 2 panggilan akses dokumen untuk memvalidasi setiap penulisan. Dalam hal ini, setiap penulisan menggunakan 2 dari 10 panggilan aksesnya dan permintaan penulisan batch menggunakan 6 dari 20 panggilan aksesnya.

Melebihi salah satu batas akan menyebabkan error izin ditolak.

Beberapa panggilan akses dokumen dapat di-cache, dan panggilan yang di-cache tidak diperhitungkan batasnya.

Kedalaman maksimum panggilan fungsi 20
Jumlah maksimum panggilan rekursif atau fungsi siklis 0 &lpar;tidak diizinkan&rpar;
Jumlah maksimum ekspresi yang dievaluasi per permintaan 1.000
Ukuran maksimum kumpulan aturan 64 KB

Cloud Storage

Struktur dasar

Aturan Keamanan Firebase di Cloud Firestore dan Cloud Storage menggunakan sintaks berikut:

service <<name>> {
  // Match the resource path.
  match <<path>> {
    // Allow the request if the following conditions are true.
    allow <<methods>> : if <<condition>>
  }
}

Berikut konsep utama yang perlu dipahami saat membuat aturan:

  • Permintaan: Metode yang diaktifkan pada pernyataan allow. Metode tersebut adalah metode yang boleh dijalankan. Metode standarnya adalah: get, list, create, update, dan delete. Metode mudah read dan write akan mengaktifkan akses baca dan tulis pada database atau path penyimpanan yang ditentukan.
  • Path: Lokasi database atau penyimpanan, direpresentasikan sebagai path URI.
  • Aturan: Pernyataan allow, berisi kondisi yang akan mengizinkan permintaan jika permintaan tersebut dinilai benar.

Mencocokkan path

Aturan Keamanan Storage match (mencocokkan) lokasi file yang digunakan untuk mengakses file di Cloud Storage. Aturan dapat match (mencocokkan) lokasi persisnya maupun lokasi karakter pengganti, dan juga dapat dijadikan bertingkat. Jika tidak ada aturan yang cocok yang memungkinkan metode permintaan, atau jika condition bernilai false, maka permintaan akan ditolak.

Pencocokan persis

// Exact match for "images/profilePhoto.png"
match /images/profilePhoto.png {
  allow write: if <condition>;
}

// Exact match for "images/croppedProfilePhoto.png"
match /images/croppedProfilePhoto.png {
  allow write: if <other_condition>;
}

Pencocokan bertingkat

// Partial match for files that start with "images"
match /images {
  // Exact match for "images/profilePhoto.png"
  match /profilePhoto.png {
    allow write: if <condition>;
  }

  // Exact match for "images/croppedProfilePhoto.png"
  match /croppedProfilePhoto.png {
    allow write: if <other_condition>;
  }
}

Pencocokan karakter pengganti

Aturan juga dapat digunakan untuk match (mencocokkan) pola dengan karakter pengganti. Karakter pengganti adalah variabel bernama yang mewakili string tunggal, seperti profilePhoto.png, atau multi-segmen, seperti images/profilePhoto.png.

Karakter pengganti dibuat dengan menambahkan tanda kurung kurawal di awal dan akhir nama, misalnya {string}. Karakter pengganti multi-segmen dapat dinyatakan dengan menambahkan =** ke nama, misalnya {path=**}:

// Partial match for files that start with "images"
match /images {
  // Exact match for "images/*"
  // e.g. images/profilePhoto.png is matched
  match /{imageId} {
    // This rule only matches a single path segment (*)
    // imageId is a string that contains the specific segment matched
    allow read: if <condition>;
  }

  // Exact match for "images/**"
  // e.g. images/users/user:12345/profilePhoto.png is matched
  // images/profilePhoto.png is also matched!
  match /{allImages=**} {
    // This rule matches one or more path segments (**)
    // allImages is a path that contains all segments matched
    allow read: if <other_condition>;
  }
}

Jika beberapa aturan cocok dengan sebuah file, maka hasilnya adalah OR untuk semua evaluasi aturan. Artinya, jika aturan yang cocok dengan file bernilai true, hasilnya adalah true.

Pada aturan di atas, file "images/profilePhoto.png" dapat dibaca jika salah satu dari condition atau other_condition bernilai true, sedangkan file "images/users/user:12345/profilePhoto.png" merupakan satu-satunya subjek dari hasil other_condition.

Variabel karakter pengganti dapat dirujuk dari dalam match yang memberikan nama file atau otorisasi lokasi:

// Another way to restrict the name of a file
match /images/{imageId} {
  allow read: if imageId == "profilePhoto.png";
}

Aturan Keamanan Storage tidak menurun dan aturan hanya dievaluasi jika lokasi permintaan cocok dengan lokasi untuk aturan yang ditetapkan.

Evaluasi permintaan

Upload, download, perubahan metadata, dan penghapusan dievaluasi menggunakan request yang dikirim ke Cloud Storage. Variabel request memuat lokasi file tempat permintaan dijalankan, waktu ketika permintaan diterima, dan nilai resource baru jika permintaan tersebut adalah permintaan tulis. Header HTTP dan status autentikasi juga dicantumkan.

Objek request juga berisi ID unik pengguna dan payload Firebase Authentication dalam objek request.auth, yang akan dijelaskan lebih lanjut di bagian Keamanan Berbasis Pengguna dalam dokumen ini.

Daftar lengkap properti pada objek request tersedia di bawah:

Properti Jenis Deskripsi
auth map<string, string> Saat pengguna login, memasukkan uid, ID unik pengguna, dan token, peta Firebase Authentication JWT diklaim. Untuk kondisi lainnya, nilainya adalah null.
params map<string, string> Peta yang memuat parameter kueri permintaan.
path path path yang mewakili lokasi tempat permintaan dijalankan.
resource map<string, string> Nilai resource baru, hanya ada pada permintaan write.
time timestamp Stempel waktu yang menunjukkan waktu server ketika permintaan dievaluasi.

Evaluasi resource

Saat mengevaluasi aturan, Anda mungkin juga ingin mengevaluasi metadata dari file yang sedang diupload, didownload, diubah, atau dihapus. Dengan begitu, Anda dapat membuat aturan yang rumit dan kuat untuk melakukan hal spesifik, seperti hanya mengizinkan upload file dengan jenis konten tertentu atau penghapusan file dengan ukuran lebih besar dari ukuran tertentu.

Aturan Keamanan Firebase untuk Cloud Storage menyediakan metadata file di objek resource, yang berisi key-value pair dari metadata yang muncul pada objek Cloud Storage. Properti ini dapat diinspeksi pada permintaan read atau write untuk memastikan keutuhan data.

Pada permintaan write (seperti upload, update metadata, dan penghapusan), selain objek resource yang memuat metadata file untuk file yang sedang berada di lokasi permintaan, Anda juga dapat menggunakan objek request.resource yang memuat subset metadata file yang akan ditulis jika operasi tulis diizinkan. Anda dapat menggunakan kedua nilai tersebut untuk memastikan keutuhan data atau memberlakukan pembatasan aplikasi, seperti jenis atau ukuran file.

Daftar lengkap properti pada objek resource tersedia di bawah:

Properti Jenis Deskripsi
name string Nama lengkap objek
bucket string Nama bucket yang ditempati objek ini.
generation int GCS object generation untuk objek ini.
metageneration int GCS object metageneration untuk objek ini.
size int Ukuran objek, dalam byte.
timeCreated timestamp Stempel waktu yang menunjukkan kapan objek dibuat.
updated timestamp Stempel waktu yang menunjukkan kapan objek terakhir diupdate.
md5Hash string Hash MD5 untuk objek ini.
crc32c string Hash crc32c untuk objek ini.
etag string etag yang terkait dengan objek ini.
contentDisposition string Disposisi konten yang terkait dengan objek ini.
contentEncoding string Encoding konten yang terkait dengan objek ini.
contentLanguage string Bahasa konten yang terkait dengan objek ini.
contentType string Jenis konten yang terkait dengan objek ini.
metadata map<string, string> Key/value pair untuk metadata khusus tambahan yang ditetapkan developer.

request.resource memuat semua properti di atas, kecuali generation, metageneration, etag, timeCreated, dan updated.

Contoh Lengkap

Dengan menggabungkan semuanya, Anda dapat membuat contoh lengkap aturan untuk solusi penyimpanan gambar:

service firebase.storage {
 match /b/{bucket}/o {
   match /images {
     // Cascade read to any image type at any path
     match /{allImages=**} {
       allow read;
     }

     // Allow write files to the path "images/*", subject to the constraints:
     // 1) File is less than 5MB
     // 2) Content type is an image
     // 3) Uploaded content type matches existing content type
     // 4) File name (stored in imageId wildcard variable) is less than 32 characters
     match /{imageId} {
       allow write: if request.resource.size < 5 * 1024 * 1024
                    && request.resource.contentType.matches('image/.*')
                    && request.resource.contentType == resource.contentType
                    && imageId.size() < 32
     }
   }
 }
}

Realtime Database

Struktur dasar

Pada Realtime Database, Aturan Keamanan Firebase terdiri dari ekspresi serupa JavaScript yang terdapat pada dokumen JSON.

Aturan tersebut menggunakan sintaks berikut:

{
  "rules": {
    "<<path>>": {
    // Allow the request if the condition for each method is true.
      ".read": <<condition>>,
      ".write": <<condition>>,
      ".validate": <<condition>>
    }
  }
}

Ada tiga elemen dasar dalam aturan:

  • Path: Lokasi database Ini mencerminkan struktur JSON database.
  • Permintaan: Ini adalah metode yang digunakan aturan untuk memberikan akses. Aturan read and write memberikan akses baca dan tulis yang luas, sedangkan aturan validate bertindak sebagai verifikasi kedua untuk memberikan akses berdasarkan data masuk atau yang sudah ada.
  • Kondisi: Kondisi yang mengizinkan permintaan jika permintaan tersebut dinilai benar.

Cara penerapan aturan pada path

Pada Realtime Database, Aturan berlaku secara atomis. Artinya, aturan di node induk tingkat yang lebih tinggi menggantikan aturan di node turunan yang lebih terperinci. Selain itu, aturan di node yang lebih dalam tidak dapat memberi akses ke path induk. Anda tidak dapat memperbaiki atau mencabut akses di path yang lebih dalam pada struktur database jika Anda sudah memberikan akses ke salah satu path induk.

Perhatikan aturan berikut ini:

{
  "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 jika /foo/ berisi node turunan baz dengan nilai true. Aturan ".read": false di dalam /foo/bar/ tidak berpengaruh di sini karena akses tidak dapat dicabut oleh lokasi turunan.

Meski terlihat tidak intuitif, struktur ini adalah bagian dari bahasa aturan yang andal. Karenanya, hak istimewa akses yang sangat kompleks dapat diterapkan dengan usaha minimal. Ini sangat berguna untuk keamanan berbasis pengguna.

Namun, aturan .validate tidak bersifat menurun. Semua aturan validasi harus terpenuhi di semua tingkat hierarkinya agar operasi tulis diizinkan.

Selain itu, karena aturan tidak berlaku untuk path induk, operasi baca atau tulis akan gagal jika lokasi yang diminta atau di lokasi induk yang memberikan akses tidak memiliki aturan. Sekalipun setiap lokasi 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, maka sepertinya mengambil lokasi /records/ akan menampilkan rec1 dan bukan rec2. Akan tetapi, 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
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
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

Oleh karena operasi baca pada /records/ bersifat atomis, dan tidak ada aturan baca yang memberikan akses ke semua data pada /records/, ini akan menyebabkan error PERMISSION_DENIED. Jika mengevaluasi aturan ini dalam simulator keamanan di Firebase console, kita bisa melihat bahwa operasi baca ditolak:

Attempt to read /records with auth=Success(null)
    /
    /records

No .read rule allowed the operation.
Read was denied.

Operasi ditolak karena tidak ada aturan baca yang mengizinkan akses ke lokasi /records/, namun perhatikan bahwa aturan untuk rec1 tidak pernah dievaluasi karena tidak berada di lokasi yang diminta. Untuk mengambil rec1, kita 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!
}];
Swift
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!

Variabel lokasi

Aturan Realtime Database mendukung variabel $location untuk mencocokkan segmen path. Gunakan awalan $ di depan segmen path untuk mencocokkan aturan dengan node turunan di sepanjang path.

  {
    "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')"
          }
        }
      }
    }
  }

Anda juga dapat menggunakan $variable bersamaan dengan nama path tetap.

  {
    "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 }
      }
    }
  }