این راهنما مبتنی بر یادگیری راهنمای زبان اصلی قوانین امنیتی 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 با پایگاه داده بیدرنگ Firebase ادغام می شود تا به شما امکان می دهد دسترسی به داده ها را بر اساس هر کاربر با استفاده از شرایط کنترل کنید. هنگامی که کاربر احراز هویت می کند، متغیر 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" } } } }
ساختار پایگاه داده خود را برای پشتیبانی از شرایط احراز هویت
ساختار پایگاه داده خود به گونه ای که نوشتن قوانین را آسان تر می کند، معمولاً مفید است. یکی از الگوهای رایج برای ذخیره داده های کاربر در پایگاه داده بیدرنگ این است که همه کاربران خود را در یک گره 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 را برمی گرداند. |
مراحل بعدی
پس از این بحث در مورد شرایط، شما درک پیچیده تری از قوانین دارید و آماده هستید:
نحوه رسیدگی به موارد استفاده اصلی را بیاموزید و گردش کار برای توسعه، آزمایش و استقرار قوانین را بیاموزید:
- درباره مجموعه کاملی از متغیرهای قوانین از پیش تعریف شده که می توانید برای ایجاد شرایط استفاده کنید، بیاموزید.
- قوانینی بنویسید که به سناریوهای رایج رسیدگی می کنند.
- با مرور موقعیت هایی که باید قوانین ناامن را شناسایی کرده و از آن اجتناب کنید، دانش خود را تقویت کنید.
- درباره مجموعه شبیه ساز محلی Firebase و نحوه استفاده از آن برای آزمایش قوانین بیاموزید.
- روش های موجود برای استقرار Rules را مرور کنید.
ویژگیهای قوانین را یاد بگیرید که مختص پایگاه داده بیدرنگ است:
- بیاموزید که چگونه پایگاه داده بیدرنگ خود را فهرست کنید .
- REST API را برای استقرار Rules مرور کنید.