تعرف على البنية الأساسية للغة Realtime Database Security Rules

تسمح لك قواعد أمان قاعدة بيانات Firebase Realtime بالتحكم في الوصول إلى البيانات المخزنة في قاعدة البيانات الخاصة بك. تسمح لك بنية القواعد المرنة بإنشاء قواعد تطابق أي شيء ، من جميع عمليات الكتابة إلى قاعدة البيانات الخاصة بك إلى العمليات على العقد الفردية.

تعد قواعد أمان قاعدة البيانات في الوقت الفعلي تكوينًا تعريفيًا لقاعدة البيانات الخاصة بك. هذا يعني أن القواعد معرّفة بشكل منفصل عن منطق المنتج. هذا له عدد من المزايا: العملاء ليسوا مسؤولين عن فرض الأمان ، ولن تؤثر تطبيقات عربات التي تجرها الدواب على بياناتك ، وربما الأهم من ذلك ، أنه لا توجد حاجة لمراجع وسيط ، مثل الخادم ، لحماية البيانات من العالم.

يصف هذا الموضوع البنية الأساسية والهيكل قواعد أمان قاعدة البيانات Realtime المستخدمة لإنشاء مجموعات قواعد كاملة.

هيكلة قواعد الأمان الخاصة بك

تتكون قواعد أمان قاعدة البيانات في الوقت الفعلي من تعبيرات شبيهة بجافا سكريبت مضمنة في مستند JSON. يجب أن يتبع هيكل القواعد الخاصة بك هيكل البيانات التي قمت بتخزينها في قاعدة البيانات الخاصة بك.

تحدد القواعد الأساسية مجموعة من العقد التي يجب تأمينها ، وطرق الوصول (على سبيل المثال ، القراءة والكتابة) المتضمنة ، والظروف التي بموجبها يُسمح بالوصول أو يُرفض. في الأمثلة التالية ، ستكون شروطنا عبارة عن عبارات true false بسيطة ، ولكن في الموضوع التالي سنغطي طرقًا أكثر ديناميكية للتعبير عن الشروط.

لذلك ، على سبيل المثال ، إذا كنا نحاول تأمين child_node ضمن parent_node ، فإن الصيغة العامة التي يجب اتباعها هي:

{
  "rules": {
    "parent_node": {
      "child_node": {
        ".read": <condition>,
        ".write": <condition>,
        ".validate": <condition>,
      }
    }
  }
}

دعونا نطبق هذا النمط. على سبيل المثال ، لنفترض أنك تتعقب قائمة من الرسائل ولديك بيانات تبدو كالتالي:

{
  "messages": {
    "message0": {
      "content": "Hello",
      "timestamp": 1405704370369
    },
    "message1": {
      "content": "Goodbye",
      "timestamp": 1405704395231
    },
    ...
  }
}

يجب أن يتم تنظيم القواعد الخاصة بك بطريقة مماثلة. فيما يلي مجموعة من القواعد الخاصة بأمان القراءة فقط والتي قد تكون منطقية لبنية البيانات هذه. يوضح هذا المثال كيف نحدد عقد قاعدة البيانات التي تنطبق عليها القواعد وشروط تقييم القواعد في تلك العقد.

{
  "rules": {
    // For requests to access the 'messages' node...
    "messages": {
      // ...and the individual wildcarded 'message' nodes beneath
      // (we'll cover wildcarding variables more a bit later)....
      "$message": {

        // For each message, allow a read operation if <condition>. In this
        // case, we specify our condition as "true", so read access is always granted.
        ".read": "true",

        // For read-only behavior, we specify that for write operations, our
        // condition is false.
        ".write": "false"
      }
    }
  }
}

عمليات القواعد الأساسية

هناك ثلاثة أنواع من القواعد لفرض الأمن بناءً على نوع العملية التي يتم إجراؤها على البيانات: .write و .read و .validate . فيما يلي ملخص سريع لأهدافهم:

أنواع القواعد
.يقرأ يصف ما إذا ومتى تم السماح للمستخدمين بقراءة البيانات.
.يكتب يصف ما إذا كان يُسمح بكتابة البيانات ومتى.
.validate يحدد الشكل الذي ستبدو عليه القيمة المنسقة بشكل صحيح ، وما إذا كانت تحتوي على سمات فرعية ، ونوع البيانات.

متغيرات التقاط أحرف البدل

تشير جميع بيانات القواعد إلى العقد. يمكن أن تشير العبارة إلى عقدة معينة أو تستخدم متغيرات التقاط أحرف البدل $ للإشارة إلى مجموعات من العقد على مستوى التسلسل الهرمي. استخدم متغيرات الالتقاط هذه لتخزين قيمة مفاتيح العقد لاستخدامها داخل عبارات القواعد اللاحقة. تتيح لك هذه التقنية كتابة شروط قواعد أكثر تعقيدًا ، وهو أمر سنتناوله بمزيد من التفصيل في الموضوع التالي.

{
  "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 }
    }
  }
}

قراءة وكتابة القواعد تتالي

تعمل قواعد .read .write من أعلى إلى أسفل ، مع القواعد الضحلة التي تتجاوز القواعد الأعمق. إذا كانت القاعدة تمنح أذونات القراءة أو الكتابة في مسار معين ، فإنها تمنح أيضًا حق الوصول إلى جميع العقد الفرعية الموجودة تحتها. ضع في اعتبارك الهيكل التالي:

{
  "rules": {
     "foo": {
        // allows read to /foo/*
        ".read": "data.child('baz').val() === true",
        "bar": {
          /* ignored, since read was allowed already */
          ".read": false
        }
     }
  }
}

تسمح بنية الأمان هذه /bar/ بالقراءة من أي وقت يحتوي /foo/ على baz طفل بقيمة true . ليس للقاعدة ".read": false ضمن /foo/bar/ أي تأثير هنا ، حيث لا يمكن إبطال الوصول بواسطة مسار فرعي.

على الرغم من أنه قد لا يبدو بديهيًا على الفور ، إلا أنه يعد جزءًا قويًا من لغة القواعد ويسمح بتنفيذ امتيازات الوصول المعقدة للغاية بأقل جهد ممكن. سيتم توضيح هذا عندما نصل إلى الأمان المستند إلى المستخدم لاحقًا في هذا الدليل.

لاحظ أن قواعد .validate لا تتالي. يجب استيفاء جميع قواعد التحقق على جميع مستويات التسلسل الهرمي للسماح بالكتابة.

القواعد ليست مرشحات

يتم تطبيق القواعد بطريقة ذرية. هذا يعني أن عملية القراءة أو الكتابة قد فشلت على الفور إذا لم تكن هناك قاعدة في ذلك الموقع أو في موقع أصلي يمنح حق الوصول. حتى إذا كان من الممكن الوصول إلى كل مسار طفل متأثر ، فإن القراءة في الموقع الأصلي ستفشل تمامًا. ضع في اعتبارك هذا الهيكل:

{
  "rules": {
    "records": {
      "rec1": {
        ".read": true
      },
      "rec2": {
        ".read": false
      }
    }
  }
}

بدون فهم أن القواعد يتم تقييمها تلقائيًا ، قد يبدو أن جلب /records/ المسار سيعيد rec1 ولكن ليس rec2 . النتيجة الفعلية ، مع ذلك ، هي خطأ:

جافا سكريبت
var db = firebase.database();
db.ref("records").once("value", function(snap) {
  // success method is not called
}, function(err) {
  // error callback triggered with PERMISSION_DENIED
});
ج موضوعية
ملاحظة: منتج Firebase هذا غير متاح في هدف مقطع التطبيق.
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
[[_ref child:@"records"] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
  // success block is not called
} withCancelBlock:^(NSError * _Nonnull error) {
  // cancel block triggered with PERMISSION_DENIED
}];
سويفت
ملاحظة: منتج Firebase هذا غير متاح في هدف مقطع التطبيق.
var ref = FIRDatabase.database().reference()
ref.child("records").observeSingleEventOfType(.Value, withBlock: { snapshot in
    // success block is not called
}, withCancelBlock: { error in
    // cancel block triggered with PERMISSION_DENIED
})
جافا
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference("records");
ref.addListenerForSingleValueEvent(new ValueEventListener() {
  @Override
  public void onDataChange(DataSnapshot snapshot) {
    // success method is not called
  }

  @Override
  public void onCancelled(FirebaseError firebaseError) {
    // error callback triggered with PERMISSION_DENIED
  });
});
استراحة
curl https://docs-examples.firebaseio.com/rest/records/
# response returns a PERMISSION_DENIED error

نظرًا لأن عملية القراءة في /records/ هي ذرية ، ولا توجد قاعدة قراءة تمنح الوصول إلى جميع البيانات الموجودة ضمن /records/ ، سيؤدي ذلك إلى ظهور خطأ PERMISSION_DENIED . إذا قمنا بتقييم هذه القاعدة في محاكي الأمان في وحدة تحكم Firebase الخاصة بنا ، فيمكننا أن نرى أنه تم رفض عملية القراءة لأنه لا توجد قاعدة قراءة تسمح بالوصول إلى /records/ المسار. ومع ذلك ، لاحظ أن قاعدة rec1 لم يتم تقييمها أبدًا لأنها لم تكن في المسار الذي طلبناه. لجلب rec1 ، نحتاج إلى الوصول إليه مباشرة:

جافا سكريبت
var db = firebase.database();
db.ref("records/rec1").once("value", function(snap) {
  // SUCCESS!
}, function(err) {
  // error callback is not called
});
ج موضوعية
ملاحظة: منتج Firebase هذا غير متاح في هدف مقطع التطبيق.
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
[[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
    // SUCCESS!
}];
سويفت
ملاحظة: منتج Firebase هذا غير متاح في هدف مقطع التطبيق.
var ref = FIRDatabase.database().reference()
ref.child("records/rec1").observeSingleEventOfType(.Value, withBlock: { snapshot in
    // SUCCESS!
})
جافا
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference("records/rec1");
ref.addListenerForSingleValueEvent(new ValueEventListener() {
  @Override
  public void onDataChange(DataSnapshot snapshot) {
    // SUCCESS!
  }

  @Override
  public void onCancelled(FirebaseError firebaseError) {
    // error callback is not called
  }
});
استراحة
curl https://docs-examples.firebaseio.com/rest/records/rec1
# SUCCESS!

بيانات متداخلة

من الممكن تطبيق أكثر من قاعدة على العقدة. في حالة تحديد تعبيرات قواعد متعددة عقدة ، يتم رفض طريقة الوصول إذا كان أي من الشروط false :

{
  "rules": {
    "messages": {
      // A rule expression that applies to all nodes in the 'messages' node
      "$message": {
        ".read": "true",
        ".write": "true"
      },
      // A second rule expression applying specifically to the 'message1` node
      "message1": {
        ".read": "false",
        ".write": "false"
      }
    }
  }
}

في المثال أعلاه ، سيتم رفض عمليات القراءة إلى عقدة message1 لأن القواعد الثانية false دائمًا ، على الرغم من أن القاعدة الأولى true دائمًا.

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

يمكنك تعميق فهمك لقواعد أمان قاعدة بيانات Firebase Realtime:

  • تعرف على المفهوم الرئيسي التالي للغة القواعد ، والشروط الديناميكية ، التي تسمح لقواعدك بفحص ترخيص المستخدم ، ومقارنة البيانات الحالية والواردة ، والتحقق من صحة البيانات الواردة ، والتحقق من بنية الاستعلامات الواردة من العميل ، والمزيد.

  • راجع حالات استخدام الأمان النموذجية وتعريفات قواعد أمان Firebase التي تتناولها .