يستند هذا الدليل إلى دليل التعرّف على دليل اللغة الأساسية لقواعد أمان Firebase. لعرض كيفية إضافة شروط إلى "قواعد أمان قاعدة بيانات Firebase" في الوقت الفعلي.
الوحدة الأساسية لقواعد أمان قاعدة بيانات الوقت الفعلي هي الشرط. حاسمة
الشرط هو تعبير منطقي يحدد ما إذا كانت عملية معينة
السماح أو الرفض. بالنسبة إلى القواعد الأساسية، يعمل استخدام القيم الثابتة true
وfalse
كأحد
الشروط بشكل جيد جدًا. ولكنّ لغة قواعد أمان "قاعدة بيانات الوقت الفعلي" تمنحك
طُرقًا لكتابة شروط أكثر تعقيدًا يمكنها:
- التحقّق من مصادقة المستخدم
- تقييم البيانات الحالية مقارنةً بالبيانات التي تم إرسالها حديثًا
- الوصول إلى أجزاء مختلفة من قاعدة بياناتك ومقارنتها
- التحقّق من صحة البيانات الواردة
- استخدام بنية طلبات البحث الواردة في منطق الأمان
استخدام متغيرات $ لالتقاط شرائح المسار
يمكنك التقاط أجزاء من المسار للقراءة أو الكتابة عن طريق الإعلان
التقاط المتغيرات باستخدام البادئة $
.
يُستخدَم هذا العنصر كحرف بدل، ويخزّن قيمة هذا المفتاح لاستخدامها داخل
شروط القواعد:
{ "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 للسماح لك بالتحكم في الوصول إلى data
على أساس كل مستخدم باستخدام الشروط. بعد أن يُجري المستخدم مصادقة، لن يجري auth
في قواعد أمان قاعدة البيانات في الوقت الفعلي، ستتم تعبئة المتغير في
المعلومات. وتشمل هذه المعلومات المعرّف الفريد (uid
)
بالإضافة إلى بيانات الحساب المرتبطة، مثل رقم تعريف Facebook أو عنوان البريد الإلكتروني، وغيرها من المعلومات. في حال استخدام مقدّم مصادقة مخصّص، يمكنك إضافة حقولك الخاصة
إلى الحمولة البرمجية لمصادقة المستخدم.
يشرح هذا القسم كيفية دمج لغة قواعد أمان قاعدة بيانات Firebase في الوقت الفعلي مع معلومات المصادقة حول المستخدمين. من خلال الجمع بين هذين المفهومين، يمكنك التحكم في الوصول إلى البيانات استنادًا إلى هوية المستخدم.
متغيّر auth
يكون المتغيّر auth
المحدّد مسبقًا في القواعد فارغًا قبل
بدء المصادقة.
بعد مصادقة المستخدم من خلال مصادقة Firebase سوف يحتوي على السمات التالية:
مزوِّد الخدمة | طريقة المصادقة المستخدمة ("password"، و"مجهول"، و"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" } } } }
تنظيم قاعدة بياناتك لتتوافق مع شروط المصادقة
من المفيد عادةً تنظيم قاعدة بياناتك بطريقة تسهّل كتابة
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
،
يمكننا الوصول إلى أي مسار كما كان قبل حدث الكتابة أو بعده.
راجِع هذا المثال الذي يسمح بعمليات الكتابة ما دامت قيمة node
/allow_writes/
هي true
، ولا تحتوي node/allow_writes/
الرئيسية على علامة
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()" } } } }
مع وضع هذه الصيغة في الاعتبار، اطّلِع على نتائج عمليات الكتابة التالية:
JavaScript
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);
Objective-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];
Swift
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);
REST
# 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()" } } } }
في هذا الصيغة، ستنجح أي من العمليات التالية:
JavaScript
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);
Objective-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];
Swift
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);
REST
# 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.
تعبيرات القواعد المستندة إلى طلبات البحث | ||
---|---|---|
التعبير | النوع | الوصف |
query.orderByKey طلب.orderByPriority query.orderByValue |
قيمة منطقية | صحيح لطلبات البحث التي يتم ترتيبها حسب المفتاح أو الأولوية أو القيمة. خطأ بخلاف ذلك. |
query.orderByChild | سلسلة null |
استخدِم سلسلة لتمثيل المسار النسبي إلى عقدة فرعية. على سبيل المثال:
query.orderByChild === "address/zip" إذا لم يكن الاستعلام
يتم ترتيبها حسب عقدة فرعية، تكون هذه القيمة فارغة.
|
query.startAt طلب البحث.endAt طلب البحث.equalTo |
سلسلة رقم منطقية null |
يسترجع حدود طلب البحث الذي يتم تنفيذه، أو يعرض قيمة فارغة إذا لم يتم ضبط حدود. |
query.limitToFirst query.limitToLast |
رقم null |
يسترجع الحدّ الأقصى المسموح به لتنفيذ الطلب، أو يعرض قيمة فارغة إذا لم يكن هناك حدّ أقصى محدّد. |
الخطوات التالية
بعد هذه المناقشة للشروط، تكون لديك فكرة أكثر تعقيدًا فهم Rules وهم على استعداد لما يلي:
تعلُّم كيفية التعامل مع حالات الاستخدام الأساسية وتعلُّم سير العمل لتطوير اختبار ونشر Rules:
- تعرَّف على المجموعة الكاملة من Rules المتغيّرات المحدّدة مسبقًا التي يمكنك استخدامها لإنشاء الشروط.
- اكتب قواعد تتناول السيناريوهات الشائعة.
- يمكنك الاستفادة من معرفتك من خلال مراجعة المواقف التي يجب فيها اكتشاف القواعد غير الآمنة وتجنُّبها.
- تعرّف على حزمة أدوات المحاكاة المحلية من Firebase وكيفية استخدامها لاختبار Rules.
- راجِع الطرق المتاحة لنشر Rules.
تعرَّف على ميزات Rules الخاصة بـ Realtime Database:
- تعرَّف على كيفية فهرسة Realtime Database.
- راجِع REST API لنشر Rules.