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

این راهنما مبتنی بر یادگیری راهنمای زبان اصلی قوانین امنیتی Firebase است تا نحوه افزودن شرایط به قوانین امنیتی پایگاه داده بیدرنگ Firebase را نشان دهد.

بلوک اصلی ساختمان قوانین امنیت پایگاه داده بیدرنگ شرط است. شرط یک عبارت بولی است که تعیین می کند آیا یک عملیات خاص باید مجاز یا رد شود. برای قوانین اساسی، استفاده از لفظ های true و false به عنوان شرایط به خوبی کار می کند. اما زبان Realtime Database Security Rules راه هایی را برای نوشتن شرایط پیچیده تری به شما ارائه می دهد که می تواند:

  • احراز هویت کاربر را بررسی کنید
  • داده های موجود را در مقابل داده های ارسالی جدید ارزیابی کنید
  • به بخش های مختلف پایگاه داده خود دسترسی پیدا کرده و با آن مقایسه کنید
  • اعتبار سنجی داده های دریافتی
  • از ساختار پرس و جوهای ورودی برای منطق امنیتی استفاده کنید

استفاده از متغیرهای $ برای گرفتن بخش های مسیر

شما می توانید بخش هایی از مسیر را برای خواندن یا نوشتن با اعلام متغیرهای ضبط با پیشوند $ ثبت کنید. این به عنوان یک کارت وحشی عمل می کند و مقدار آن کلید را برای استفاده در شرایط قوانین ذخیره می کند:

{
  "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 ادغام می شود تا به شما امکان می دهد دسترسی به داده ها را بر اساس هر کاربر با استفاده از شرایط کنترل کنید. هنگامی که کاربر احراز هویت می کند، متغیر auth در قوانین امنیت پایگاه داده بیدرنگ شما با اطلاعات کاربر پر می شود. این اطلاعات شامل شناسه منحصر به فرد آنها ( uid ) و همچنین داده‌های حساب پیوندی، مانند شناسه فیس‌بوک یا آدرس ایمیل، و سایر اطلاعات است. اگر ارائه‌دهنده تأیید اعتبار سفارشی را پیاده‌سازی کنید، می‌توانید فیلدهای خود را به بارگذاری تأیید اعتبار کاربر خود اضافه کنید.

این بخش نحوه ترکیب زبان قوانین امنیتی پایگاه داده بیدرنگ Firebase را با اطلاعات احراز هویت کاربران خود توضیح می دهد. با ترکیب این دو مفهوم، می توانید دسترسی به داده ها را بر اساس هویت کاربر کنترل کنید.

متغیر auth

متغیر auth از پیش تعریف شده در قوانین قبل از احراز هویت باطل است.

هنگامی که یک کاربر با احراز هویت Firebase احراز هویت شد، دارای ویژگی های زیر خواهد بود:

ارائه دهنده روش احراز هویت مورد استفاده ("رمز عبور"، "ناشناس"، "فیس بوک"، "github"، "google"، یا "twitter").
uid یک شناسه کاربری منحصر به فرد، تضمین شده است که در همه ارائه دهندگان منحصر به فرد است.
نشانه محتویات کد 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 به توسعه‌دهندگان اجازه می‌دهد تا ادعاهایی را برای کاربر 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 هستند. قوانین زیر را در نظر بگیرید:

{
  "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);
هدف-C
توجه: این محصول 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);
هدف-C
توجه: این محصول 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" . اگر پرس و جو توسط یک گره فرزند مرتب نشده باشد، این مقدار null است.
query.startAt
query.endAt
query.equalTo
رشته
شماره
بولی
تهی
محدوده‌های کوئری اجرا را بازیابی می‌کند، یا اگر مجموعه‌ای محدود وجود نداشته باشد، null را برمی‌گرداند.
query.limitToFirst
query.limitToLast
شماره
تهی
محدودیت پرس و جو در حال اجرا را بازیابی می کند یا اگر محدودیتی تنظیم نشده باشد، null را برمی گرداند.

مراحل بعدی

پس از این بحث در مورد شرایط، شما درک پیچیده تری از Rules دارید و آماده هستید:

نحوه رسیدگی به موارد استفاده اصلی را بیاموزید و گردش کار برای توسعه، آزمایش و استقرار Rules بیاموزید:

ویژگی‌های Rules یاد بگیرید که مختص Realtime Database است: