This guide builds on the learn the core Firebase Security Rules language guide to show how to add conditions to your Firebase Realtime Database Security Rules.
بلوک اصلی سازندهی قوانین امنیتی پایگاه دادهی بلادرنگ، شرط است. شرط یک عبارت بولی است که تعیین میکند آیا یک عملیات خاص باید مجاز یا رد شود. برای قوانین پایه، استفاده از لیترالهای true و false به عنوان شرط کاملاً خوب عمل میکند. اما زبان قوانین امنیتی پایگاه دادهی بلادرنگ به شما روشهایی برای نوشتن شرطهای پیچیدهتر میدهد که میتوانند:
- بررسی احراز هویت کاربر
- Evaluate existing data against newly-submitted data
- Access and compare different parts of your database
- اعتبارسنجی دادههای ورودی
- Use the structure of incoming queries for security logic
استفاده از متغیرهای $ برای ثبت بخشهای مسیر
شما میتوانید با تعریف متغیرهای 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 }
}
}
}احراز هویت
One of the most common security rule patterns is controlling access based on the user's authentication state. For example, your app may want to allow only signed-in users to write data.
اگر برنامه شما از احراز هویت Firebase استفاده میکند، متغیر request.auth شامل اطلاعات احراز هویت برای کلاینتی است که دادهها را درخواست میکند. برای اطلاعات بیشتر در مورد request.auth ، به مستندات مرجع مراجعه کنید.
Firebase Authentication با Firebase Realtime Database ادغام میشود تا به شما امکان دهد دسترسی به دادهها را بر اساس هر کاربر با استفاده از شرایط کنترل کنید. پس از احراز هویت کاربر، متغیر auth در قوانین امنیتی پایگاه داده بلادرنگ شما با اطلاعات کاربر پر میشود. این اطلاعات شامل شناسه منحصر به فرد ( uid ) و همچنین دادههای حساب مرتبط، مانند شناسه فیسبوک یا آدرس ایمیل و سایر اطلاعات است. اگر یک ارائه دهنده احراز هویت سفارشی پیادهسازی کنید، میتوانید فیلدهای خود را به بار احراز هویت کاربر خود اضافه کنید.
این بخش نحوه ترکیب زبان قوانین امنیتی پایگاه داده Realtime Firebase با اطلاعات احراز هویت کاربران شما را توضیح میدهد. با ترکیب این دو مفهوم، میتوانید دسترسی به دادهها را بر اساس هویت کاربر کنترل کنید.
متغیر auth
The predefined auth variable in the rules is null before authentication takes place.
Once a user is authenticated with Firebase Authentication it will contain the following attributes:
| ارائه دهنده | The authentication method used ("password", "anonymous", "facebook", "github", "google", or "twitter"). |
| شناسه کاربری | A unique user id, guaranteed to be unique across all providers. |
| توکن | The contents of the Firebase Auth ID token. See the reference documentation for auth.token for more details. |
Here is an example rule that uses the auth variable to ensure that each user can only write to a user-specific path:
{
"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"
}
}
}
}Structuring Your Database to Support Authentication Conditions
معمولاً مفید است که پایگاه داده خود را به گونهای ساختار دهید که نوشتن 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"
}
}
} Developers creating their own custom authentication tokens can optionally add claims to these tokens. These claims are available on the auth.token variable in your rules.
دادههای موجود در مقابل دادههای جدید
متغیر data از پیش تعریف شده برای اشاره به دادهها قبل از انجام عملیات نوشتن استفاده میشود. برعکس، متغیر newData شامل دادههای جدیدی است که در صورت موفقیتآمیز بودن عملیات نوشتن وجود خواهند داشت. newData نشان دهنده نتیجه ادغام شده دادههای جدید در حال نوشتن و دادههای موجود است.
To illustrate, this rule would allow us to create new records or delete existing ones, but not to make changes to existing non-null data:
// 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()"
ارجاع دادهها در مسیرهای دیگر
Any data can be used as criterion for rules. Using the predefined variables root , data , and newData , we can access any path as it would exist before or after a write event.
این مثال را در نظر بگیرید که عملیات نوشتن را تا زمانی که مقدار گره /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 باشد).
These might seem like trivial points, but are in fact significant features for writing powerful Firebase Realtime Database Security Rules. Consider the following rules:
{
"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()"
}
}
}
}With this variant in mind, look at the results for the following write operations:
جاوا اسکریپت
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);
هدف-سی
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];
سویفت
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 Now let's look at the same structure, but using .write rules instead of .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()" } } } }
In this variant, any of the following operations would succeed:
جاوا اسکریپت
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 *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];
سویفت
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
}
The following query, which includes the query parameters in the rule, would succeed:
db.ref("baskets").orderByChild("owner")
.equalTo(auth.currentUser.uid)
.on("value", cb) // Would succeed
However, queries that do not include the parameters in the rule would fail with a PermissionDenied error:
db.ref("baskets").on("value", cb) // Would fail with PermissionDenied
You can also use query-based rules to limit how much data a client downloads through read operations.
For example, the following rule limits read access to only the first 1000 results of a query, as ordered by priority:
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)
The following query. expressions are available in 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 که میتوانید برای ساخت شرطها استفاده کنید، اطلاعات کسب کنید.
- قوانینی بنویسید که سناریوهای رایج را پوشش دهند.
- با مرور موقعیتهایی که باید قوانین ناامن را تشخیص داده و از آنها اجتناب کنید ، دانش خود را افزایش دهید.
- درباره مجموعه شبیهساز محلی Firebase و نحوه استفاده از آن برای آزمایش Rules کسب کنید.
- روشهای موجود برای استقرار Rules را بررسی کنید.
ویژگیهای Rules را که مختص Realtime Database هستند، بیاموزید:
- یاد بگیرید که چگونه Realtime Database خود را فهرست بندی کنید .
- API REST را برای استقرار Rules مرور کنید.