این راهنما مبتنی بر یادگیری راهنمای زبان اصلی قوانین امنیتی Firebase است تا نحوه افزودن شرایط به قوانین امنیتی پایگاه داده بیدرنگ Firebase را نشان دهد.
بلوک اصلی ساختمان قوانین امنیت پایگاه داده بیدرنگ شرط است. شرط یک عبارت بولی است که تعیین می کند آیا یک عملیات خاص باید مجاز یا رد شود. برای قوانین اساسی، استفاده از لفظ های true
و false
به عنوان شرایط به خوبی کار می کند. اما زبان Realtime Database Security 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 } } } }
احراز هویت
یکی از رایج ترین الگوهای قوانین امنیتی، کنترل دسترسی بر اساس وضعیت احراز هویت کاربر است. برای مثال، برنامه شما ممکن است بخواهد فقط به کاربرانی که وارد سیستم شده اند اجازه نوشتن داده را بدهد.
اگر برنامه شما از Firebase Authentication استفاده می کند، متغیر request.auth
حاوی اطلاعات احراز هویت برای مشتری درخواست کننده داده است. برای اطلاعات بیشتر در مورد request.auth
، به مستندات مرجع مراجعه کنید.
Firebase Authentication با Firebase Realtime Database ادغام می شود تا به شما امکان می دهد دسترسی به داده ها را بر اساس هر کاربر با استفاده از شرایط کنترل کنید. هنگامی که کاربر احراز هویت می کند، متغیر auth
در قوانین امنیت پایگاه داده بیدرنگ شما با اطلاعات کاربر پر می شود. این اطلاعات شامل شناسه منحصر به فرد آنها ( uid
) و همچنین دادههای حساب پیوندی، مانند شناسه فیسبوک یا آدرس ایمیل، و سایر اطلاعات است. اگر ارائهدهنده تأیید اعتبار سفارشی را پیادهسازی کنید، میتوانید فیلدهای خود را به بارگذاری تأیید اعتبار کاربر خود اضافه کنید.
این بخش نحوه ترکیب زبان قوانین امنیتی پایگاه داده بیدرنگ Firebase را با اطلاعات احراز هویت کاربران خود توضیح می دهد. با ترکیب این دو مفهوم، می توانید دسترسی به داده ها را بر اساس هویت کاربر کنترل کنید.
متغیر auth
متغیر auth
از پیش تعریف شده در قوانین قبل از احراز هویت باطل است.
هنگامی که یک کاربر با احراز هویت Firebase احراز هویت شد، دارای ویژگی های زیر خواهد بود:
ارائه دهنده | روش احراز هویت مورد استفاده ("رمز عبور"، "ناشناس"، "فیس بوک"، "github"، "google"، یا "twitter"). |
uid | یک شناسه کاربری منحصر به فرد، تضمین شده است که در همه ارائه دهندگان منحصر به فرد است. |
نشانه | محتویات کد Firebase Auth ID. برای جزئیات بیشتر به مستندات مرجع برای auth.token مراجعه کنید. |
در اینجا یک قانون مثال است که از متغیر auth
استفاده می کند تا اطمینان حاصل شود که هر کاربر فقط می تواند در یک مسیر خاص کاربر بنویسد:
{ "rules": { "users": { "$user_id": { // grants write access to the owner of this user account // whose uid must exactly match the key ($user_id) ".write": "$user_id === auth.uid" } } } }
ساختار پایگاه داده خود را برای پشتیبانی از شرایط احراز هویت
ساختار پایگاه داده خود به گونه ای که نوشتن Rules آسان تر می کند، معمولاً مفید است. یکی از الگوهای رایج برای ذخیره داده های کاربر در Realtime Database این است که همه کاربران خود را در یک گره users
ذخیره کنید که فرزندان آن مقادیر uid
برای هر کاربر هستند. اگر میخواهید دسترسی به این دادهها را محدود کنید تا فقط کاربر وارد شده بتواند دادههای خود را ببیند، قوانین شما چیزی شبیه به این خواهند بود.
{ "rules": { "users": { "$uid": { ".read": "auth !== null && auth.uid === $uid" } } } }
کار با ادعاهای سفارشی احراز هویت
برای برنامههایی که به کنترل دسترسی سفارشی برای کاربران مختلف نیاز دارند، Firebase Authentication به توسعهدهندگان اجازه میدهد تا ادعاهایی را برای کاربر Firebase تنظیم کنند . این ادعاها در متغیر auth.token
در قوانین شما قابل دسترسی هستند. در اینجا نمونه ای از قوانینی است که از ادعای سفارشی hasEmergencyTowel
استفاده می کنند:
{ "rules": { "frood": { // A towel is about the most massively useful thing an interstellar // hitchhiker can have ".read": "auth.token.hasEmergencyTowel === true" } } }
توسعه دهندگانی که توکن های احراز هویت سفارشی خود را ایجاد می کنند، می توانند به صورت اختیاری ادعاهایی را به این توکن ها اضافه کنند. این ادعاها روی متغیر auth.token
در قوانین شما موجود است.
داده های موجود در مقابل داده های جدید
متغیر data
از پیش تعریف شده برای ارجاع به داده ها قبل از انجام عملیات نوشتن استفاده می شود. برعکس، متغیر newData
حاوی داده های جدیدی است که در صورت موفقیت آمیز بودن عملیات نوشتن، وجود خواهند داشت. newData
نتیجه ادغام داده های جدید نوشته شده و داده های موجود را نشان می دهد.
برای نشان دادن، این قانون به ما اجازه می دهد تا رکوردهای جدید ایجاد کنیم یا رکوردهای موجود را حذف کنیم، اما تغییراتی در داده های غیر تهی موجود ایجاد نکنیم:
// we can write as long as old data or new data does not exist // in other words, if this is a delete or a create, but not an update ".write": "!data.exists() || !newData.exists()"
ارجاع داده ها در مسیرهای دیگر
هر داده ای را می توان به عنوان معیاری برای قوانین استفاده کرد. با استفاده از متغیرهای از پیش تعریف شده root
، data
و newData
، میتوانیم به هر مسیری که قبل یا بعد از یک رویداد نوشتن وجود دارد دسترسی داشته باشیم.
این مثال را در نظر بگیرید، که تا زمانی که مقدار گره /allow_writes/
true
است، عملیات نوشتن اجازه می دهد، گره والد یک مجموعه پرچم readOnly
ندارد و فرزندی به نام foo
در داده های جدید نوشته شده وجود دارد:
".write": "root.child('allow_writes').val() === true && !data.parent().child('readOnly').exists() && newData.child('foo').exists()"
اعتبارسنجی داده ها
اجرای ساختارهای داده و اعتبارسنجی قالب و محتوای دادهها باید با استفاده از قوانین .validate
انجام شود که تنها پس از اینکه یک قانون .write
موفق به اعطای دسترسی شود اجرا میشوند. در زیر نمونهای از تعریف قانون .validate
است که فقط به تاریخها در قالب YYYY-MM-DD بین سالهای 1900-2099 اجازه میدهد، که با استفاده از یک عبارت منظم بررسی میشود.
".validate": "newData.isString() && newData.val().matches(/^(19|20)[0-9][0-9][-\\/. ](0[1-9]|1[012])[-\\/. ](0[1-9]|[12][0-9]|3[01])$/)"
قوانین .validate
تنها نوع قاعده امنیتی هستند که آبشار نمی شوند. اگر هر یک از قوانین اعتبارسنجی در هر رکورد فرزند با شکست مواجه شود، کل عملیات نوشتن رد خواهد شد. بهعلاوه، وقتی دادهها حذف میشوند، تعاریف اعتبارسنجی نادیده گرفته میشوند (یعنی وقتی مقدار جدیدی که نوشته میشود null
است).
اینها ممکن است نکات بی اهمیتی به نظر برسند، اما در واقع ویژگی های مهمی برای نوشتن قوانین امنیتی قدرتمند پایگاه داده بیدرنگ Firebase هستند. قوانین زیر را در نظر بگیرید:
{ "rules": { // write is allowed for all paths ".write": true, "widget": { // a valid widget must have attributes "color" and "size" // allows deleting widgets (since .validate is not applied to delete rules) ".validate": "newData.hasChildren(['color', 'size'])", "size": { // the value of "size" must be a number between 0 and 99 ".validate": "newData.isNumber() && newData.val() >= 0 && newData.val() <= 99" }, "color": { // the value of "color" must exist as a key in our mythical // /valid_colors/ index ".validate": "root.child('valid_colors/' + newData.val()).exists()" } } } }
با در نظر گرفتن این نوع، به نتایج عملیات نوشتن زیر نگاه کنید:
جاوا اسکریپت
var ref = db.ref("/widget"); // PERMISSION_DENIED: does not have children color and size ref.set('foo'); // PERMISSION DENIED: does not have child color ref.set({size: 22}); // PERMISSION_DENIED: size is not a number ref.set({ size: 'foo', color: 'red' }); // SUCCESS (assuming 'blue' appears in our colors list) ref.set({ size: 21, color: 'blue'}); // If the record already exists and has a color, this will // succeed, otherwise it will fail since newData.hasChildren(['color', 'size']) // will fail to validate ref.child('size').set(99);
هدف-C
FIRDatabaseReference *ref = [[[FIRDatabase database] reference] child: @"widget"]; // PERMISSION_DENIED: does not have children color and size [ref setValue: @"foo"]; // PERMISSION DENIED: does not have child color [ref setValue: @{ @"size": @"foo" }]; // PERMISSION_DENIED: size is not a number [ref setValue: @{ @"size": @"foo", @"color": @"red" }]; // SUCCESS (assuming 'blue' appears in our colors list) [ref setValue: @{ @"size": @21, @"color": @"blue" }]; // If the record already exists and has a color, this will // succeed, otherwise it will fail since newData.hasChildren(['color', 'size']) // will fail to validate [[ref child:@"size"] setValue: @99];
سویفت
var ref = FIRDatabase.database().reference().child("widget") // PERMISSION_DENIED: does not have children color and size ref.setValue("foo") // PERMISSION DENIED: does not have child color ref.setValue(["size": "foo"]) // PERMISSION_DENIED: size is not a number ref.setValue(["size": "foo", "color": "red"]) // SUCCESS (assuming 'blue' appears in our colors list) ref.setValue(["size": 21, "color": "blue"]) // If the record already exists and has a color, this will // succeed, otherwise it will fail since newData.hasChildren(['color', 'size']) // will fail to validate ref.child("size").setValue(99);
جاوا
FirebaseDatabase database = FirebaseDatabase.getInstance(); DatabaseReference ref = database.getReference("widget"); // PERMISSION_DENIED: does not have children color and size ref.setValue("foo"); // PERMISSION DENIED: does not have child color ref.child("size").setValue(22); // PERMISSION_DENIED: size is not a number Map<String,Object> map = new HashMap<String, Object>(); map.put("size","foo"); map.put("color","red"); ref.setValue(map); // SUCCESS (assuming 'blue' appears in our colors list) map = new HashMap<String, Object>(); map.put("size", 21); map.put("color","blue"); ref.setValue(map); // If the record already exists and has a color, this will // succeed, otherwise it will fail since newData.hasChildren(['color', 'size']) // will fail to validate ref.child("size").setValue(99);
استراحت
# PERMISSION_DENIED: does not have children color and size curl -X PUT -d 'foo' \ https://docs-examples.firebaseio.com/rest/securing-data/example.json # PERMISSION DENIED: does not have child color curl -X PUT -d '{"size": 22}' \ https://docs-examples.firebaseio.com/rest/securing-data/example.json # PERMISSION_DENIED: size is not a number curl -X PUT -d '{"size": "foo", "color": "red"}' \ https://docs-examples.firebaseio.com/rest/securing-data/example.json # SUCCESS (assuming 'blue' appears in our colors list) curl -X PUT -d '{"size": 21, "color": "blue"}' \ https://docs-examples.firebaseio.com/rest/securing-data/example.json # If the record already exists and has a color, this will # succeed, otherwise it will fail since newData.hasChildren(['color', 'size']) # will fail to validate curl -X PUT -d '99' \ https://docs-examples.firebaseio.com/rest/securing-data/example/size.json
حالا بیایید به همان ساختار نگاه کنیم، اما از قوانین .write
جای .validate
استفاده کنیم:
{ "rules": { // this variant will NOT allow deleting records (since .write would be disallowed) "widget": { // a widget must have 'color' and 'size' in order to be written to this path ".write": "newData.hasChildren(['color', 'size'])", "size": { // the value of "size" must be a number between 0 and 99, ONLY IF WE WRITE DIRECTLY TO SIZE ".write": "newData.isNumber() && newData.val() >= 0 && newData.val() <= 99" }, "color": { // the value of "color" must exist as a key in our mythical valid_colors/ index // BUT ONLY IF WE WRITE DIRECTLY TO COLOR ".write": "root.child('valid_colors/'+newData.val()).exists()" } } } }
در این نوع، هر یک از عملیات زیر موفق خواهد بود:
جاوا اسکریپت
var ref = new Firebase(URL + "/widget"); // ALLOWED? Even though size is invalid, widget has children color and size, // so write is allowed and the .write rule under color is ignored ref.set({size: 99999, color: 'red'}); // ALLOWED? Works even if widget does not exist, allowing us to create a widget // which is invalid and does not have a valid color. // (allowed by the write rule under "color") ref.child('size').set(99);
هدف-C
Firebase *ref = [[Firebase alloc] initWithUrl:URL]; // ALLOWED? Even though size is invalid, widget has children color and size, // so write is allowed and the .write rule under color is ignored [ref setValue: @{ @"size": @9999, @"color": @"red" }]; // ALLOWED? Works even if widget does not exist, allowing us to create a widget // which is invalid and does not have a valid color. // (allowed by the write rule under "color") [[ref childByAppendingPath:@"size"] setValue: @99];
سویفت
var ref = Firebase(url:URL) // ALLOWED? Even though size is invalid, widget has children color and size, // so write is allowed and the .write rule under color is ignored ref.setValue(["size": 9999, "color": "red"]) // ALLOWED? Works even if widget does not exist, allowing us to create a widget // which is invalid and does not have a valid color. // (allowed by the write rule under "color") ref.childByAppendingPath("size").setValue(99)
جاوا
Firebase ref = new Firebase(URL + "/widget"); // ALLOWED? Even though size is invalid, widget has children color and size, // so write is allowed and the .write rule under color is ignored Map<String,Object> map = new HashMap<String, Object>(); map.put("size", 99999); map.put("color", "red"); ref.setValue(map); // ALLOWED? Works even if widget does not exist, allowing us to create a widget // which is invalid and does not have a valid color. // (allowed by the write rule under "color") ref.child("size").setValue(99);
استراحت
# ALLOWED? Even though size is invalid, widget has children color and size, # so write is allowed and the .write rule under color is ignored curl -X PUT -d '{size: 99999, color: "red"}' \ https://docs-examples.firebaseio.com/rest/securing-data/example.json # ALLOWED? Works even if widget does not exist, allowing us to create a widget # which is invalid and does not have a valid color. # (allowed by the write rule under "color") curl -X PUT -d '99' \ https://docs-examples.firebaseio.com/rest/securing-data/example/size.json
این تفاوت بین قوانین .write
و .validate
را نشان می دهد. همانطور که نشان داده شد، همه این قوانین باید با استفاده از .validate
نوشته شوند، به استثنای قانون newData.hasChildren()
که بستگی به این دارد که آیا حذف باید مجاز باشد یا خیر.
قوانین مبتنی بر پرس و جو
اگرچه نمیتوانید از قوانین بهعنوان فیلتر استفاده کنید ، میتوانید دسترسی به زیرمجموعههای داده را با استفاده از پارامترهای پرس و جو در قوانین خود محدود کنید. از query.
استفاده کنید عباراتی در قوانین شما برای اعطای دسترسی خواندن یا نوشتن بر اساس پارامترهای پرس و جو.
به عنوان مثال، قانون مبتنی بر پرس و جو زیر از قوانین امنیتی مبتنی بر کاربر و قوانین مبتنی بر پرس و جو استفاده می کند تا دسترسی به داده های مجموعه baskets
را فقط به سبدهای خریدی که کاربر فعال دارد محدود کند:
"baskets": {
".read": "auth.uid !== null &&
query.orderByChild === 'owner' &&
query.equalTo === auth.uid" // restrict basket access to owner of basket
}
پرس و جو زیر که شامل پارامترهای پرس و جو در قانون است، موفق خواهد بود:
db.ref("baskets").orderByChild("owner")
.equalTo(auth.currentUser.uid)
.on("value", cb) // Would succeed
با این حال، پرس و جوهایی که پارامترهای قانون را شامل نمی شوند با خطای PermissionDenied
شکست می خورند:
db.ref("baskets").on("value", cb) // Would fail with PermissionDenied
همچنین میتوانید از قوانین مبتنی بر پرسوجو برای محدود کردن میزان دادهای که مشتری از طریق عملیات خواندن دانلود میکند، استفاده کنید.
به عنوان مثال، قانون زیر دسترسی خواندن را به 1000 نتیجه اول یک پرس و جو، طبق اولویت، محدود می کند:
messages: {
".read": "query.orderByKey &&
query.limitToFirst <= 1000"
}
// Example queries:
db.ref("messages").on("value", cb) // Would fail with PermissionDenied
db.ref("messages").limitToFirst(1000)
.on("value", cb) // Would succeed (default order by key)
query.
عبارات در قوانین امنیت پایگاه داده بیدرنگ در دسترس هستند.
عبارات قانون مبتنی بر پرس و جو | ||
---|---|---|
بیان | تایپ کنید | توضیحات |
query.orderByKey query.orderByPriority query.orderByValue | بولی | درست برای جستارهای مرتب شده بر اساس کلید، اولویت یا مقدار. در غیر این صورت نادرست است. |
query.orderByChild | رشته تهی | از یک رشته برای نشان دادن مسیر نسبی به یک گره فرزند استفاده کنید. برای مثال query.orderByChild === "address/zip" . اگر پرس و جو توسط یک گره فرزند مرتب نشده باشد، این مقدار null است. |
query.startAt query.endAt query.equalTo | رشته شماره بولی تهی | محدودههای کوئری اجرا را بازیابی میکند، یا اگر مجموعهای محدود وجود نداشته باشد، null را برمیگرداند. |
query.limitToFirst query.limitToLast | شماره تهی | محدودیت پرس و جو در حال اجرا را بازیابی می کند یا اگر محدودیتی تنظیم نشده باشد، null را برمی گرداند. |
مراحل بعدی
پس از این بحث در مورد شرایط، شما درک پیچیده تری از Rules دارید و آماده هستید:
نحوه رسیدگی به موارد استفاده اصلی را بیاموزید و گردش کار برای توسعه، آزمایش و استقرار Rules بیاموزید:
- درباره مجموعه کاملی از متغیرهای Rules از پیش تعریف شده که می توانید برای ایجاد شرایط استفاده کنید، بیاموزید.
- قوانینی بنویسید که به سناریوهای رایج رسیدگی می کنند.
- با مرور موقعیت هایی که باید قوانین ناامن را شناسایی کرده و از آن اجتناب کنید ، دانش خود را تقویت کنید.
- درباره مجموعه شبیه ساز محلی Firebase و نحوه استفاده از آن برای آزمایش Rules بیاموزید.
- روش های موجود برای استقرار Rules مرور کنید.
ویژگیهای Rules یاد بگیرید که مختص Realtime Database است:
- بیاموزید که چگونه Realtime Database خود را فهرست کنید .
- REST API را برای استقرار Rules مرور کنید.