از شرایط در قوانین امنیت پایگاه داده بلادرنگ استفاده کنید

این راهنما بر اساس راهنمای زبان یادگیری قوانین امنیتی 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);
هدف-سی
توجه: این محصول Firebase در App Clip target در دسترس نیست.
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 target در دسترس نیست.
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 target در دسترس نیست.
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 target در دسترس نیست.
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 که مختص Realtime Database هستند، بیاموزید: