استخدم الشروط في قواعد أمان قاعدة البيانات في الوقت الفعلي

يعتمد هذا الدليل على تعلم دليل اللغة الأساسي لقواعد أمان Firebase لإظهار كيفية إضافة شروط إلى قواعد أمان قاعدة بيانات Firebase Realtime.

العنصر الأساسي لقواعد أمان قاعدة البيانات في الوقت الفعلي هو الشرط . الشرط هو تعبير منطقي يحدد ما إذا كان يجب السماح بعملية معينة أم رفضها. بالنسبة للقواعد الأساسية، فإن استخدام القيم الحرفية 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، فإن المتغير 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 هو تخزين جميع المستخدمين في عقدة 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);
ج موضوعية
ملاحظة: منتج Firebase هذا غير متوفر في هدف App Clip.
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];
سويفت
ملاحظة: منتج Firebase هذا غير متوفر في هدف App Clip.
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 هذا غير متوفر في هدف App Clip.
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];
سويفت
ملاحظة: منتج Firebase هذا غير متوفر في هدف App Clip.
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" . إذا لم يتم ترتيب الاستعلام بواسطة عقدة فرعية، فستكون هذه القيمة فارغة.
query.startAt
query.endAt
query.equalTo
خيط
رقم
منطقية
باطل
استرداد حدود الاستعلام المنفذ، أو إرجاع قيمة فارغة إذا لم تكن هناك مجموعة مرتبطة.
query.limitToFirst
query.limitToLast
رقم
باطل
استرداد الحد الأقصى للاستعلام المنفذ، أو إرجاع قيمة فارغة إذا لم يتم تعيين حد.

الخطوات التالية

بعد هذه المناقشة للشروط، سيكون لديك فهم أكثر تعقيدًا للقواعد وستكون جاهزًا للقيام بما يلي:

تعرف على كيفية التعامل مع حالات الاستخدام الأساسية، وتعرف على سير العمل لتطوير القواعد واختبارها ونشرها:

تعرف على ميزات القواعد الخاصة بقاعدة بيانات Realtime: