این راهنما بر اساس راهنمای زبان یادگیری قوانین امنیتی Firebase ساخته شده است تا نحوه اضافه کردن شرطها به قوانین امنیتی پایگاه داده Realtime Firebase شما را نشان دهد.
بلوک اصلی سازندهی قوانین امنیتی پایگاه دادهی بلادرنگ، شرط است. شرط یک عبارت بولی است که تعیین میکند آیا یک عملیات خاص باید مجاز یا رد شود. برای قوانین پایه، استفاده از لیترالهای true
و false
به عنوان شرط کاملاً خوب عمل میکند. اما زبان قوانین امنیتی پایگاه دادهی بلادرنگ به شما روشهایی برای نوشتن شرطهای پیچیدهتر میدهد که میتوانند:
- بررسی احراز هویت کاربر
- ارزیابی دادههای موجود در مقابل دادههای جدید ارسالی
- به بخشهای مختلف پایگاه داده خود دسترسی پیدا کنید و آنها را با هم مقایسه کنید
- اعتبارسنجی دادههای ورودی
- از ساختار کوئریهای ورودی برای منطق امنیتی استفاده کنید
استفاده از متغیرهای $ برای ثبت بخشهای مسیر
شما میتوانید با تعریف متغیرهای capture با پیشوند $
، بخشهایی از مسیر را برای خواندن یا نوشتن ضبط کنید. این به عنوان یک wild card عمل میکند و مقدار آن کلید را برای استفاده در شرایط قوانین ذخیره میکند:
{ "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 استفاده میکند، متغیر request.auth
شامل اطلاعات احراز هویت برای کلاینتی است که دادهها را درخواست میکند. برای اطلاعات بیشتر در مورد request.auth
، به مستندات مرجع مراجعه کنید.
Firebase Authentication با Firebase Realtime Database ادغام میشود تا به شما امکان دهد دسترسی به دادهها را بر اساس هر کاربر با استفاده از شرایط کنترل کنید. پس از احراز هویت کاربر، متغیر auth
در قوانین امنیتی پایگاه داده بلادرنگ شما با اطلاعات کاربر پر میشود. این اطلاعات شامل شناسه منحصر به فرد ( uid
) و همچنین دادههای حساب مرتبط، مانند شناسه فیسبوک یا آدرس ایمیل و سایر اطلاعات است. اگر یک ارائه دهنده احراز هویت سفارشی پیادهسازی کنید، میتوانید فیلدهای خود را به بار احراز هویت کاربر خود اضافه کنید.
این بخش نحوه ترکیب زبان قوانین امنیتی پایگاه داده Realtime Firebase با اطلاعات احراز هویت کاربران شما را توضیح میدهد. با ترکیب این دو مفهوم، میتوانید دسترسی به دادهها را بر اساس هویت کاربر کنترل کنید.
متغیر auth
متغیر از پیش تعریف شده auth
در قوانین، قبل از انجام احراز هویت، null است.
پس از احراز هویت کاربر با Firebase Authentication، ویژگیهای زیر در آن ثبت میشوند:
ارائه دهنده | روش احراز هویت مورد استفاده ("رمز عبور"، "ناشناس"، "فیسبوک"، "گیتهاب"، "گوگل" یا "توییتر"). |
شناسه کاربری | یک شناسه کاربری منحصر به فرد، که تضمین میشود در بین همه ارائه دهندگان منحصر به فرد باشد. |
توکن | محتویات توکن 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 به توسعهدهندگان اجازه میدهد تا ادعاهایی (Claims) را برای یک کاربر فایربیس تنظیم کنند . این ادعاها در متغیر auth.token
در قوانین شما قابل دسترسی هستند. در اینجا مثالی از قوانینی که از ادعای سفارشی hasEmergencyTowel
استفاده میکنند، آورده شده است:
{ "rules": { "frood": { // A towel is about the most massively useful thing an interstellar // hitchhiker can have ".read": "auth.token.hasEmergencyTowel === true" } } }
توسعهدهندگانی که توکنهای احراز هویت سفارشی خود را ایجاد میکنند، میتوانند به صورت اختیاری ادعاهایی (Claims) را به این توکنها اضافه کنند. این ادعاها در متغیر 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 بین سالهای ۱۹۰۰-۲۰۹۹ را مجاز میداند، که با استفاده از یک عبارت منظم بررسی میشود.
".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 Realtime هستند. قوانین زیر را در نظر بگیرید:
{ "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);
هدف-سی
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);
هدف-سی
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
همچنین میتوانید از قوانین مبتنی بر پرسوجو برای محدود کردن میزان دادههایی که یک کلاینت از طریق عملیات خواندن دانلود میکند، استفاده کنید.
برای مثال، قانون زیر دسترسی خواندن را فقط به ۱۰۰۰ نتیجه اول یک پرسوجو، بر اساس اولویت، محدود میکند:
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.
زیر در قوانین امنیتی پایگاه داده بلادرنگ (Realtime Database Security Rules) موجود هستند.
عبارات قاعده مبتنی بر پرس و جو | ||
---|---|---|
بیان | نوع | توضیحات |
پرس و جو.orderByKey query.orderByPriority پرس و جو.سفارش بر اساس مقدار | بولی | برای پرسوجوهایی که بر اساس کلید، اولویت یا مقدار مرتب شدهاند، مقدار درست (true) و در غیر این صورت مقدار نادرست (false) است. |
پرس و جو.سفارش توسط فرزند | رشته تهی | از یک رشته برای نمایش مسیر نسبی به یک گره فرزند استفاده کنید. برای مثال، query.orderByChild === "address/zip" . اگر پرسوجو بر اساس گره فرزند مرتب نشده باشد، این مقدار null است. |
پرس و جو.startAt پرس و جو.پایان query.equalTo | رشته شماره بولی تهی | مرزهای پرسوجوی در حال اجرا را بازیابی میکند، یا اگر هیچ مجموعهای از مرزها وجود نداشته باشد، مقدار null را برمیگرداند. |
query.limitToFirst query.limitToLast | شماره تهی | محدودیت مربوط به اجرای کوئری را بازیابی میکند، یا اگر محدودیتی تعیین نشده باشد، مقدار null را برمیگرداند. |
مراحل بعدی
بعد از این بحث در مورد شرایط، شما درک پیچیدهتری از Rules دارید و آمادهاید تا:
یاد بگیرید که چگونه موارد استفاده اصلی را مدیریت کنید و گردش کار را برای توسعه، آزمایش و استقرار Rules بیاموزید:
- درباره مجموعه کامل متغیرهای از پیش تعریف شده Rules که میتوانید برای ساخت شرطها استفاده کنید، اطلاعات کسب کنید.
- قوانینی بنویسید که سناریوهای رایج را پوشش دهند.
- با مرور موقعیتهایی که باید قوانین ناامن را تشخیص داده و از آنها اجتناب کنید ، دانش خود را افزایش دهید.
- درباره مجموعه شبیهساز محلی Firebase و نحوه استفاده از آن برای آزمایش Rules اطلاعات کسب کنید.
- روشهای موجود برای استقرار Rules را بررسی کنید.
ویژگیهای Rules که مختص Realtime Database هستند، بیاموزید:
- یاد بگیرید که چگونه Realtime Database خود را فهرست بندی کنید .
- API REST را برای استقرار Rules مرور کنید.