Keamanan dapat menjadi salah satu bagian paling kompleks dari proses pengembangan aplikasi. Pada sebagian besar aplikasi, developer harus mem-build 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 jalur 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 aturannya.
Cloud Firestore
Struktur dasar
Aturan Keamanan Firebase di Cloud Firestore dan Cloud Storage menggunakan struktur dan sintaksis 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 mem-build aturan:
- Permintaan: Satu atau beberapa metode yang dipanggil dalam pernyataan
allow
. Ini adalah metode yang Anda izinkan untuk berjalan. Metode standarnya adalah:get
,list
,create
,update
, dandelete
. Metoderead
danwrite
yang praktis akan mengaktifkan akses baca dan tulis yang luas pada jalur database atau penyimpanan yang ditentukan. - Jalur: Lokasi database atau penyimpanan, direpresentasikan sebagai jalur URI.
- Aturan: Pernyataan
allow
, berisi kondisi yang mengizinkan permintaan jika kondisi tersebut bernilai true (benar).
Aturan keamanan versi 2
Mulai bulan Mei 2019, aturan keamanan Firebase versi 2 kini tersedia. Aturan versi 2 mengubah perilaku karakter pengganti
berulang {name=**}
. Anda harus menggunakan versi 2 jika berencana untuk menggunakan kueri grup koleksi. Anda harus memilih
versi 2 dengan menerapkan rules_version = '2';
di baris pertama dalam aturan
keamanan Anda:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
Mencocokkan jalur
Semua pernyataan kecocokan harus mengarah ke dokumen, bukan koleksi. Pernyataan kecocokan dapat mengarah ke dokumen tertentu, seperti di match /cities/SF
atau menggunakan karakter pengganti untuk mengarah ke dokumen apa pun di jalur yang ditentukan, seperti di match /cities/{city}
.
Pada contoh di atas, pernyataan kecocokan menggunakan sintaksis karakter pengganti {city}
.
Artinya, aturan tersebut berlaku untuk setiap dokumen dalam koleksi cities
, seperti /cities/SF
atau /cities/NYC
. Saat ekspresi allow
dalam pernyataan kecocokan dievaluasi, variabel city
akan diselesaikan menjadi nama dokumen kota, seperti SF
atau NYC
.
Mencocokkan subkoleksi
Data di Cloud Firestore disusun menjadi koleksi dokumen, dan setiap dokumen dapat memperluas hierarkinya hingga subkoleksi. Anda perlu memahami interaksi antara aturan keamanan dan data hierarkis.
Ambil contoh situasi ketika setiap dokumen dalam koleksi cities
berisi subkoleksi landmarks
. Aturan keamanan hanya berlaku di jalur 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>;
}
}
}
}
Saat membuat pernyataan match
bertingkat, jalur pernyataan match
dalam selalu bersifat relatif terhadap jalur 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>;
}
}
}
Karakter pengganti berulang
Jika Anda ingin menerapkan aturan ke hierarki yang dalam secara arbitrer, gunakan sintaksis karakter pengganti berulang, {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 sintaksis karakter pengganti berulang, 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 terletak di /cities/SF/landmarks/coit_tower
, dan nilai variabel document
akan menjadi SF/landmarks/coit_tower
.
Namun, perlu diperhatikan bahwa perilaku karakter pengganti berulang bergantung pada versi aturan.
Versi 1
Aturan keamanan menggunakan versi 1 secara default. Dalam versi 1, karakter pengganti berulang cocok dengan satu atau beberapa item jalur. Karakter pengganti tersebut tidak cocok dengan jalur yang kosong, sehingga match /cities/{city}/{document=**}
cocok dengan dokumen di subkoleksi, bukan di koleksi cities
, sedangkan match /cities/{document=**}
cocok dengan dokumen dalam koleksi dan subkoleksi cities
.
Karakter pengganti berulang harus ada di akhir pernyataan yang cocok.
Versi 2
Pada aturan keamanan versi 2, karakter pengganti berulang cocok dengan nol atau beberapa item jalur. match/cities/{city}/{document=**}
mencocokkan dokumen di subkoleksi apa pun serta dokumen di koleksi cities
.
Anda harus memilih versi 2 dengan menambahkan rules_version = '2';
di bagian atas aturan keamanan Anda:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Matches any document in the cities collection as well as any document
// in a subcollection.
match /cities/{city}/{document=**} {
allow read, write: if <condition>;
}
}
}
Anda dapat memiliki maksimal satu karakter pengganti berulang di setiap pernyataan kecocokan, tetapi dalam versi 2, Anda dapat menempatkan karakter pengganti ini di mana saja dalam pernyataan kecocokan. Contoh:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Matches any document in the songs collection group
match /{path=**}/songs/{song} {
allow read, write: if <condition>;
}
}
}
Jika menggunakan kueri grup koleksi, Anda harus menggunakan versi 2. Baca bagian mengamankan kueri grup koleksi.
Pernyataan kecocokan yang tumpang tindih
Ada kemungkinan dokumen cocok dengan beberapa pernyataan match
. Jika beberapa ekspresi allow
cocok dengan suatu permintaan, akses akan diizinkan jika salah satu kondisi tersebut adalah 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 operasi baca dan tulis ke koleksi cities
akan diizinkan karena aturan kedua selalu true
, meskipun aturan pertama selalu false
.
Batas aturan keamanan
Perhatikan batas berikut saat menangani aturan keamanan:
Batas | Detail |
---|---|
Jumlah maksimum panggilan exists() , get() , dan getAfter() per permintaan |
Melebihi salah satu batas akan menyebabkan error izin ditolak. Beberapa panggilan akses dokumen dapat dimasukkan cache, dan panggilan yang masuk cache tidak diperhitungkan dalam batas tersebut. |
Kedalaman maksimum pernyataan match bertingkat |
10 |
Panjang jalur maksimum, pada segmen jalur, yang diizinkan dalam sekumpulan pernyataan
match bertingkat |
100 |
Jumlah maksimum variabel tangkapan jalur yang diizinkan dalam sekumpulan pernyataan
match bertingkat |
20 |
Kedalaman maksimum panggilan fungsi | 20 |
Jumlah maksimum argumen fungsi | 7 |
Jumlah maksimum binding variabel let per fungsi |
10 |
Jumlah maksimum panggilan fungsi siklis atau berulang | 0 (tidak diizinkan) |
Jumlah maksimum ekspresi yang dievaluasi per permintaan | 1.000 |
Ukuran maksimum kumpulan aturan | Kumpulan aturan harus mematuhi dua batas ukuran:
|
Cloud Storage
Struktur dasar
Aturan Keamanan Firebase di Cloud Firestore dan Cloud Storage menggunakan struktur dan sintaksis 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 mem-build aturan:
- Permintaan: Satu atau beberapa metode yang dipanggil dalam pernyataan
allow
. Ini adalah metode yang Anda izinkan untuk berjalan. Metode standarnya adalah:get
,list
,create
,update
, dandelete
. Metoderead
danwrite
yang praktis akan mengaktifkan akses baca dan tulis yang luas pada jalur database atau penyimpanan yang ditentukan. - Jalur: Lokasi database atau penyimpanan, direpresentasikan sebagai jalur URI.
- Aturan: Pernyataan
allow
, berisi kondisi yang mengizinkan permintaan jika kondisi tersebut bernilai true (benar).
Mencocokkan jalur
Aturan Keamanan Cloud Storage cocok (match
) dengan jalur file yang digunakan untuk mengakses file di Cloud Storage. Aturan dapat cocok (match
) dengan jalur persis atau jalur karakter pengganti, dan
aturan juga dapat dijadikan bertingkat. Jika tidak ada aturan kecocokan yang mengizinkan metode permintaan, atau jika
kondisi bernilai false
, 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 mencocokkan (match
) dengan pola menggunakan karakter pengganti. Karakter pengganti adalah
variabel bernama yang mewakili string tunggal, seperti
profilePhoto.png
, atau beberapa segmen jalur, seperti
images/profilePhoto.png
.
Karakter pengganti dibuat dengan menambahkan tanda kurung kurawal di awal dan akhir nama karakter pengganti,
misalnya {string}
. Karakter pengganti beberapa segmen dapat dideklarasikan dengan menambahkan =**
ke
nama karakter pengganti, seperti {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,
hasilnya adalah OR
dari hasil 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 condition
atau other_condition
bernilai true (benar), sedangkan file "images/users/user:12345/profilePhoto.png" hanya bergantung pada hasil other_condition
.
Variabel karakter pengganti dapat dirujuk dari dalam match
yang memberikan
nama file atau otorisasi jalur:
// Another way to restrict the name of a file match /images/{imageId} { allow read: if imageId == "profilePhoto.png"; }
Aturan Keamanan Cloud Storage tidak bersifat menurun, dan aturan hanya dievaluasi jika jalur permintaan cocok dengan jalur yang memiliki aturan yang ditetapkan.
Evaluasi permintaan
Upload, download, perubahan metadata, dan penghapusan dievaluasi menggunakan
request
yang dikirim ke Cloud Storage. Variabel request
berisi
jalur file tempat permintaan dijalankan, waktu 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 di objek request.auth
, yang akan dijelaskan lebih lanjut di bagian Autentikasi dokumentasi.
Daftar lengkap properti di objek request
tersedia di bawah:
Properti | Jenis | Deskripsi |
---|---|---|
auth |
map<string, string> | Saat pengguna login, memberikan uid , ID unik pengguna, dan
token , peta klaim JWT Firebase Authentication. Jika pengguna tidak login, nilainya adalah
null . |
params |
map<string, string> | Peta yang memuat parameter kueri permintaan. |
path |
jalur | path yang mewakili jalur 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, sebaiknya Anda juga mengevaluasi metadata file yang 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 integritas data.
Pada permintaan write
(seperti upload, pembaruan metadata, dan penghapusan),
selain objek resource
, yang berisi metadata file untuk file
yang saat ini ada di jalur permintaan, Anda juga dapat menggunakan objek
request.resource
, yang berisi subset metadata file yang
akan ditulis jika operasi tulis diizinkan. Anda dapat menggunakan kedua nilai tersebut untuk
memastikan integritas data atau memberlakukan pembatasan aplikasi, seperti jenis atau ukuran file.
Daftar lengkap properti di objek resource
tersedia di bawah:
Properti | Jenis | Deskripsi |
---|---|---|
name |
string | Nama lengkap objek |
bucket |
string | Nama bucket yang ditempati objek ini. |
generation |
int | Google Cloud Storage object generation untuk objek ini. |
metageneration |
int | Google Cloud Storage 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 diperbarui. |
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 kustom tambahan yang ditetapkan developer. |
request.resource
berisi semua properti di atas kecuali generation
, metageneration
, etag
, timeCreated
, dan updated
.
Batas Aturan Keamanan
Perhatikan batas berikut saat menangani aturan keamanan:
Batas | Detail |
---|---|
Jumlah maksimum panggilan firestore.exists() , firestore.get() , dan per permintaan |
2 untuk permintaan dokumen tunggal dan permintaan kueri. Melebihi batas ini akan menyebabkan error izin ditolak. Panggilan akses ke dokumen yang sama mungkin akan disimpan dalam cache, dan panggilan yang di-cache tidak diperhitungkan dalam batas ini. |
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 sintaksis berikut:
{
"rules": {
"<<path>>": {
// Allow the request if the condition for each method is true.
".read": <<condition>>,
".write": <<condition>>,
".validate": <<condition>>
}
}
}
Aturan memiliki tiga elemen dasar, yaitu:
- Jalur: Lokasi database. Ini mencerminkan struktur JSON database Anda.
- Permintaan: Ini adalah metode yang digunakan aturan untuk memberikan akses. Aturan
read
danwrite
memberikan akses baca dan tulis yang luas, sedangkan aturanvalidate
bertindak sebagai verifikasi sekunder untuk memberikan akses berdasarkan data yang masuk atau yang sudah ada. - Kondisi: Kondisi yang mengizinkan permintaan jika kondisi tersebut bernilai true (benar).
Cara penerapan aturan pada jalur
Pada Realtime Database, Aturan berlaku secara atomis. Artinya, aturan di node induk yang lebih tinggi menggantikan aturan di node turunan yang lebih terperinci. Selain itu, aturan di node yang lebih dalam tidak dapat memberi akses ke jalur induk. Anda tidak dapat menyempurnakan atau mencabut akses di jalur yang lebih dalam pada struktur database jika Anda sudah memberikan akses ke salah satu jalur 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 baz
turunan dengan nilai true
.
Aturan ".read": false
pada bagian /foo/bar/
tidak berpengaruh di sini, karena akses tidak dapat dicabut oleh jalur turunan.
Meski terlihat tidak intuitif, struktur ini adalah bagian yang andal dari bahasa aturan. 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 hierarki agar operasi tulis diizinkan.
Selain itu, karena aturan tidak berlaku untuk jalur induk, operasi baca atau tulis akan gagal jika lokasi yang diminta atau lokasi induk yang memberikan akses tidak memiliki aturan. 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
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
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 aturan ini dievaluasi dalam simulator keamanan di Firebase console, kita dapat melihat bahwa operasi baca ditolak:
Attempt to read /records with auth=Success(null) / /records No .read rule allowed the operation. Read was denied.
Operasi tersebut ditolak karena tidak ada aturan baca yang mengizinkan akses ke jalur /records/
. Namun, perhatikan bahwa aturan untuk rec1
tidak pernah dievaluasi karena tidak berada di jalur yang diminta. 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! }];
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 jalur. Gunakan awalan $
di depan segmen jalur
untuk mencocokkan aturan dengan node turunan di sepanjang jalur.
{
"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
secara bersamaan dengan
nama jalur 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 }
}
}
}