يعتمد هذا الدليل على دليل اللغة الأساسي لقواعد أمان Firebase لإظهار كيفية إضافة شروط إلى قواعد أمان قاعدة بيانات Firebase Realtime.
الشرط هو العنصر الأساسي الأساسي لقواعد أمان قاعدة بيانات Realtime. الشرط هو تعبير منطقي يحدد ما إذا كان يجب السماح بعملية معينة أو رفضها. بالنسبة للقواعد الأساسية ، فإن استخدام القيم الحرفية true
false
كشرط يعمل بشكل جيد. لكن لغة قواعد أمان قاعدة البيانات Realtime تمنحك طرقًا لكتابة شروط أكثر تعقيدًا يمكنها:
- تحقق من مصادقة المستخدم
- تقييم البيانات الموجودة مقابل البيانات المقدمة حديثًا
- الوصول ومقارنة أجزاء مختلفة من قاعدة البيانات الخاصة بك
- تحقق من صحة البيانات الواردة
- استخدم بنية الاستعلامات الواردة لمنطق الأمان
استخدام المتغيرات $ لالتقاط مقاطع المسار
يمكنك التقاط أجزاء من المسار للقراءة أو الكتابة عن طريق التصريح عن متغيرات الالتقاط بالبادئة $
. هذا بمثابة حرف بدل ، ويخزن قيمة هذا المفتاح للاستخدام داخل شروط القواعد:
{ "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 مع قاعدة بيانات Firebase Realtime للسماح لك بالتحكم في الوصول إلى البيانات على أساس كل مستخدم باستخدام الشروط. بمجرد قيام المستخدم بالمصادقة ، سيتم ملء متغير auth
في قواعد قواعد أمان قاعدة بيانات Realtime بمعلومات المستخدم. تتضمن هذه المعلومات المعرف الفريد ( uid
) بالإضافة إلى بيانات الحساب المرتبطة ، مثل معرف Facebook أو عنوان البريد الإلكتروني ، ومعلومات أخرى. إذا قمت بتنفيذ موفر مصادقة مخصص ، فيمكنك إضافة الحقول الخاصة بك إلى حمولة المصادقة الخاصة بالمستخدم.
يوضح هذا القسم كيفية دمج لغة قواعد أمان قاعدة بيانات Firebase Realtime مع معلومات المصادقة الخاصة بمستخدميك. من خلال الجمع بين هذين المفهومين ، يمكنك التحكم في الوصول إلى البيانات بناءً على هوية المستخدم.
متغير auth
متغير auth
المحدد مسبقًا في القواعد فارغ قبل حدوث المصادقة.
بمجرد مصادقة المستخدم باستخدام مصادقة Firebase ، ستحتوي على السمات التالية:
مزود | طريقة المصادقة المستخدمة ("كلمة المرور" أو "مجهول" أو "facebook" أو "github" أو "google" أو "twitter"). |
uid | معرف مستخدم فريد ، مضمون أن يكون فريدًا عبر جميع مقدمي الخدمة. |
رمز | محتويات رمز معرف مصادقة Firebase. راجع الوثائق المرجعية لـ 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" } } } }
هيكلة قاعدة البيانات الخاصة بك لدعم شروط المصادقة
من المفيد عادة هيكلة قاعدة البيانات الخاصة بك بطريقة تجعل كتابة القواعد أسهل. أحد الأنماط الشائعة لتخزين بيانات المستخدم في Realtime Database هو تخزين جميع المستخدمين في عقدة users
واحدة تكون أبناؤها هي قيم uid
لكل مستخدم. إذا كنت ترغب في تقييد الوصول إلى هذه البيانات بحيث يمكن للمستخدم الذي قام بتسجيل الدخول فقط رؤية بياناته الخاصة ، فستبدو القواعد الخاصة بك على هذا النحو.
{ "rules": { "users": { "$uid": { ".read": "auth !== null && auth.uid === $uid" } } } }
العمل مع مطالبات المصادقة المخصصة
بالنسبة للتطبيقات التي تتطلب تحكمًا مخصصًا في الوصول لمستخدمين مختلفين ، تسمح مصادقة Firebase للمطورين بتعيين مطالبات على مستخدم 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 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
يمكنك أيضًا استخدام القواعد المستندة إلى الاستعلام لتحديد مقدار البيانات التي يقوم العميل بتنزيلها من خلال عمليات القراءة.
على سبيل المثال ، تحدد القاعدة التالية حق الوصول للقراءة إلى أول 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.
تتوفر التعبيرات في قواعد أمان قاعدة البيانات Realtime.
تعبيرات القواعد المستندة إلى الاستعلام | ||
---|---|---|
تعبير | يكتب | وصف |
الاستعلام. OrderByKey الاستعلام. ترتيب حسب الأولوية الاستعلام. OrderByValue | قيمة منطقية | صواب للاستعلامات المرتبة حسب المفتاح أو الأولوية أو القيمة. خطأ خلاف ذلك. |
الاستعلام. OrderByChild | خيط باطل | استخدم سلسلة لتمثيل المسار النسبي لعقدة فرعية. على سبيل المثال ، query.orderByChild === "address/zip" . إذا لم يتم ترتيب الاستعلام بواسطة عقدة فرعية ، فستكون هذه القيمة خالية. |
query.startAt query.endAt query.equalTo | خيط رقم قيمة منطقية باطل | يسترجع حدود الاستعلام المنفذ ، أو يعيد قيمة فارغة إذا لم تكن هناك مجموعة محددة. |
query.limitToFirst الاستعلام.حد إلى آخر | رقم باطل | يسترجع الحد الأقصى للاستعلام المنفذ ، أو يعيد القيمة فارغة إذا لم يكن هناك حد معين. |
الخطوات التالية
بعد هذه المناقشة حول الشروط ، لديك فهم أكثر تعقيدًا للقواعد وأنت مستعد لما يلي:
تعرف على كيفية التعامل مع حالات الاستخدام الأساسية ، وتعلم سير العمل لتطوير واختبار ونشر القواعد:
- تعرف على المجموعة الكاملة من متغيرات القواعد المحددة مسبقًا التي يمكنك استخدامها لبناء الشروط .
- اكتب القواعد التي تتناول السيناريوهات الشائعة .
- قم بالبناء على معرفتك من خلال مراجعة المواقف التي يجب عليك فيها تحديد القواعد غير الآمنة وتجنبها .
- تعرف على مجموعة Firebase Local Emulator Suite وكيف يمكنك استخدامها لاختبار القواعد .
- راجع الطرق المتاحة لنشر القواعد .
تعلم ميزات القواعد الخاصة بقاعدة بيانات Realtime:
- تعرف على كيفية فهرسة قاعدة بيانات Realtime .
- راجع REST API لنشر القواعد .