قوانین امنیتی پایگاه داده بلادرنگ Firebase به شما امکان میدهد دسترسی به دادههای ذخیره شده در پایگاه داده خود را کنترل کنید. سینتکس انعطافپذیر قوانین به شما امکان میدهد قوانینی ایجاد کنید که با هر چیزی مطابقت داشته باشند، از همه نوشتهها در پایگاه داده گرفته تا عملیات روی گرههای منفرد.
قوانین امنیتی پایگاه داده بلادرنگ، پیکربندیهای اعلانی برای پایگاه داده شما هستند. این بدان معناست که قوانین جدا از منطق محصول تعریف میشوند. این امر مزایای متعددی دارد: کلاینتها مسئول اجرای امنیت نیستند، پیادهسازیهای دارای باگ، دادههای شما را به خطر نمیاندازند و شاید مهمتر از همه، نیازی به یک داور واسط، مانند سرور، برای محافظت از دادهها در برابر جهان نیست.
این مبحث، سینتکس و ساختار پایه قوانین امنیتی پایگاه داده Realtime را که برای ایجاد مجموعه قوانین کامل استفاده میشوند، شرح میدهد.
ساختاردهی قوانین امنیتی شما
قوانین امنیتی پایگاه داده بلادرنگ از عباراتی شبیه به جاوا اسکریپت تشکیل شدهاند که در یک سند JSON قرار دارند. ساختار قوانین شما باید از ساختار دادههایی که در پایگاه داده خود ذخیره کردهاید، پیروی کند.
قوانین اساسی، مجموعهای از گرهها که باید ایمن شوند، روشهای دسترسی (مثلاً خواندن، نوشتن) مربوطه و شرایطی که تحت آن دسترسی مجاز یا رد میشود را مشخص میکنند. در مثالهای بعدی، شرایط ما عبارات سادهی true و false خواهند بود، اما در مبحث بعدی روشهای پویاتری برای بیان شرایط را پوشش خواهیم داد.
بنابراین، برای مثال، اگر میخواهیم یک child_node تحت parent_node ایمن کنیم، سینتکس کلی که باید دنبال شود به صورت زیر است:
{
"rules": {
"parent_node": {
"child_node": {
".read": <condition>,
".write": <condition>,
".validate": <condition>,
}
}
}
}بیایید این الگو را اعمال کنیم. برای مثال، فرض کنید شما در حال پیگیری لیستی از پیامها هستید و دادههایی دارید که به این شکل هستند:
{
"messages": {
"message0": {
"content": "Hello",
"timestamp": 1405704370369
},
"message1": {
"content": "Goodbye",
"timestamp": 1405704395231
},
...
}
}قوانین شما نیز باید به شیوهای مشابه ساختار یافته باشند. در اینجا مجموعهای از قوانین برای امنیت فقط خواندنی وجود دارد که ممکن است برای این ساختار داده منطقی باشد. این مثال نحوه تعیین گرههای پایگاه داده که قوانین بر آنها اعمال میشود و شرایط ارزیابی قوانین در آن گرهها را نشان میدهد.
{ "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" } } } }
قوانین اساسی عملیات
سه نوع قانون برای اجرای امنیت بر اساس نوع عملیاتی که روی دادهها انجام میشود وجود دارد: .write ، .read و .validate . در اینجا خلاصهای سریع از اهداف آنها آورده شده است:
| انواع قوانین | |
|---|---|
| .خواندن | شرح میدهد که آیا و چه زمانی کاربران اجازه خواندن دادهها را دارند. |
| .نوشتن | شرح میدهد که آیا و چه زمانی اجازه نوشتن دادهها وجود دارد. |
| اعتبارسنجی | تعریف میکند که یک مقدار با قالببندی صحیح چگونه خواهد بود، آیا دارای ویژگیهای فرزند است یا خیر، و نوع داده را نیز مشخص میکند. |
متغیرهای ضبط شده با Wildcard
همه دستورات قوانین به گرهها اشاره میکنند. یک دستور میتواند به یک گره خاص اشاره کند یا از متغیرهای ضبط wildcard $ برای اشاره به مجموعهای از گرهها در یک سطح از سلسله مراتب استفاده کند. از این متغیرهای ضبط برای ذخیره مقدار کلیدهای گره برای استفاده در دستورات قوانین بعدی استفاده کنید. این تکنیک به شما امکان میدهد شرایط Rules پیچیدهتری بنویسید، چیزی که در مبحث بعدی با جزئیات بیشتری به آن خواهیم پرداخت.
{ "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')" } } } } }
متغیرهای پویای $ همچنین میتوانند به صورت موازی با نامهای مسیر ثابت استفاده شوند. در این مثال، ما از متغیر $other برای اعلام یک قانون .validate استفاده میکنیم که تضمین میکند widget هیچ فرزندی به جز title و color ندارد. هرگونه نوشتن که منجر به ایجاد فرزندان اضافی شود، با شکست مواجه خواهد شد.
{
"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 }
}
}
}آبشار قوانین خواندن و نوشتن
قوانین .read و .write از بالا به پایین کار میکنند، به طوری که قوانین سطحیتر، قوانین عمیقتر را لغو میکنند. اگر یک قانون، مجوزهای خواندن یا نوشتن را در یک مسیر خاص اعطا کند، به تمام گرههای فرزند زیر آن نیز دسترسی میدهد. ساختار زیر را در نظر بگیرید:
{
"rules": {
"foo": {
// allows read to /foo/*
".read": "data.child('baz').val() === true",
"bar": {
/* ignored, since read was allowed already */
".read": false
}
}
}
} این ساختار امنیتی اجازه میدهد تا /bar/ هر زمان که /foo/ حاوی یک baz فرزند با مقدار true باشد، خوانده شود. دستور ".read": false در /foo/bar/ در اینجا هیچ تاثیری ندارد، زیرا دسترسی توسط یک مسیر فرزند قابل لغو نیست.
اگرچه ممکن است در نگاه اول شهودی به نظر نرسد، اما این بخش قدرتمندی از زبان قوانین است و امکان پیادهسازی امتیازات دسترسی بسیار پیچیده را با حداقل تلاش فراهم میکند. این موضوع در ادامه این راهنما، وقتی به امنیت مبتنی بر کاربر میپردازیم، نشان داده خواهد شد.
توجه داشته باشید که قوانین .validate به صورت آبشاری اجرا نمیشوند. برای اینکه نوشتن مجاز باشد، باید تمام قوانین validate در تمام سطوح سلسله مراتب رعایت شوند.
قوانین فیلتر نیستند
قوانین به صورت اتمیک اعمال میشوند. این بدان معناست که اگر در آن مکان یا در مکان والد، قانونی برای اعطای دسترسی وجود نداشته باشد، عملیات خواندن یا نوشتن بلافاصله با شکست مواجه میشود. حتی اگر هر مسیر فرزند آسیبدیده قابل دسترسی باشد، خواندن در مکان والد کاملاً با شکست مواجه خواهد شد. این ساختار را در نظر بگیرید:
{
"rules": {
"records": {
"rec1": {
".read": true
},
"rec2": {
".read": false
}
}
}
} بدون درک اینکه قوانین به صورت اتمی ارزیابی میشوند، ممکن است به نظر برسد که واکشی مسیر /records/ rec1 برمیگرداند اما rec2 برنمیگرداند. با این حال، نتیجه واقعی یک خطا است:
جاوا اسکریپت
var db = firebase.database(); db.ref("records").once("value", function(snap) { // success method is not called }, function(err) { // error callback triggered with PERMISSION_DENIED });
هدف-سی
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 }];
سویفت
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 })
جاوا
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 }); });
استراحت
curl https://docs-examples.firebaseio.com/rest/records/ # response returns a PERMISSION_DENIED error
از آنجایی که عملیات خواندن در /records/ اتمیک است و هیچ قانون خواندنی وجود ندارد که دسترسی به تمام دادههای زیر /records/ را مجاز کند، این امر خطای PERMISSION_DENIED ایجاد میکند. اگر این قانون را در شبیهساز امنیتی در کنسول Firebase خود ارزیابی کنیم، میتوانیم ببینیم که عملیات خواندن رد شده است زیرا هیچ قانون خواندنی اجازه دسترسی به مسیر /records/ را نداده است. با این حال، توجه داشته باشید که قانون rec1 هرگز ارزیابی نشد زیرا در مسیری که ما درخواست کردیم نبود. برای دریافت rec1 ، باید مستقیماً به آن دسترسی پیدا کنیم:
جاوا اسکریپت
var db = firebase.database(); db.ref("records/rec1").once("value", function(snap) { // SUCCESS! }, function(err) { // error callback is not called });
هدف-سی
FIRDatabaseReference *ref = [[FIRDatabase database] reference]; [[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) { // SUCCESS! }];
سویفت
var ref = FIRDatabase.database().reference() ref.child("records/rec1").observeSingleEventOfType(.Value, withBlock: { snapshot in // SUCCESS! })
جاوا
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 } });
استراحت
curl https://docs-examples.firebaseio.com/rest/records/rec1 # SUCCESS!
همپوشانی اظهارات
ممکن است بیش از یک قانون برای یک گره اعمال شود. در صورتی که چندین عبارت قانون، یک گره را شناسایی کنند، اگر هر یک از شرایط 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"
}
}
}
}در مثال بالا، خواندن گره message1 رد میشود زیرا قانون دوم همیشه false است، حتی اگر قانون اول همیشه true باشد.
مراحل بعدی
میتوانید درک خود را از قوانین امنیتی پایگاه داده Firebase Realtime عمیقتر کنید:
مفهوم اصلی بعدی زبان Rules ، یعنی شرایط پویا را بیاموزید، که به Rules شما اجازه میدهد تا مجوز کاربر را بررسی کنند، دادههای موجود و ورودی را مقایسه کنند، دادههای ورودی را اعتبارسنجی کنند، ساختار پرسوجوهای دریافتی از کلاینت را بررسی کنند و موارد دیگر.
موارد استفاده امنیتی معمول و تعاریف قوانین امنیتی Firebase که به آنها میپردازد را مرور کنید.