Check out what’s new from Firebase@ Google I/O 2021, and join our alpha program for early access to the new Remote Config personalization feature. Learn more

השתמש בתנאים בכללי מסדי נתונים בזמן אמת

מדריך זה מתבסס על למד את מדריך השפה המרכזי של כללי האבטחה של Firebase כדי להראות כיצד להוסיף תנאים לכללי האבטחה בזמן אמת של מסד הנתונים של Firebase.

אבן הבניין העיקרית של כללי האבטחה של מסדי נתונים בזמן אמת היא התנאי . תנאי הוא ביטוי בוליאני הקובע אם יש לאפשר או לדחות פעולה מסוימת. לקבלת כללים בסיסיים, באמצעות true ואת false literals כתנאים עובד prefectly היטב. אך שפת כללי האבטחה של מסד הנתונים בזמן אמת נותנת לך דרכים לכתוב תנאים מורכבים יותר שיכולים:

  • בדוק אימות משתמש
  • הערך נתונים קיימים מול נתונים שהוגשו לאחרונה
  • גש והשווה חלקים שונים במסד הנתונים שלך
  • אמת את הנתונים הנכנסים
  • השתמש במבנה של שאילתות נכנסות לצורך הגיון אבטחה

שימוש ב- $ משתנים ללכידת פלחי נתיב

באפשרותך ללכוד חלקים מהנתיב לקריאה או כתיבה על ידי הצהרת משתני לכידה באמצעות הקידומת $ . זה משמש כקלף כללי ושומר את הערך של מפתח זה לשימוש בתנאי הכללים:

{
  "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 כדי לאפשר לך לשלוט בגישה לנתונים על בסיס משתמש לפי תנאים. פעם מאמת המשתמש, auth המשתנה בכללים כללי אבטחת מסד זמן אמת שלך יהיה מאוכלס לפרטי המשתמש. מידע זה כולל את המזהה הייחודי שלהם ( uid ) כמו גם נתוני חשבון מקושרים, כגון מזהה פייסבוק או כתובת דוא"ל ומידע אחר. אם אתה מיישם ספק אימות מותאם אישית, באפשרותך להוסיף שדות משלך לעומס המטען האמיתי של המשתמש שלך.

סעיף זה מסביר כיצד לשלב את שפת כללי האבטחה של מסד נתונים בזמן אמת של Firebase עם פרטי אימות על המשתמשים שלך. על ידי שילוב של שני מושגים אלה, תוכלו לשלוט בגישה לנתונים על סמך זהות המשתמש.

משתנה auth

הערך המוגדר מראש auth המשתנה בכללים הוא null לפני אימות מתרחש.

ברגע שמשתמש מאומת באמצעות אימות Firebase הוא יכיל את התכונות הבאות:

ספק שיטת האימות בה נעשה שימוש ("סיסמה", "אנונימית", "פייסבוק", "github", "google" או "טוויטר").
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"
      }
    }
  }
}

בניית מסד הנתונים שלך לתמיכה בתנאי אימות

בדרך כלל מועיל לבנות את מסד הנתונים באופן שמקל על כתיבת הכללים. דפוס נפוץ אחד לאחסון נתוני משתמשים במסד הנתונים בזמן אמת הוא לאחסן את כל המשתמשים שלך בצומת users יחיד שילדיו הם ערכי ה- uid עבור כל משתמש. אם תרצה להגביל את הגישה לנתונים אלה כך שרק המשתמש המחובר יוכל לראות את הנתונים שלו, הכללים שלך ייראו בערך כך.

{
  "rules": {
    "users": {
      "$uid": {
        ".read": "auth != null && auth.uid == $uid"
      }
    }
  }
}

עבודה עם תביעות מותאמות אישית לאימות

עבור יישומים הדורשים בקרת גישה מותאמת אישית עבור משתמשים שונים, אימות Firebase מאפשר למפתחים להגדיר תביעות על משתמש Firebase . טענות אלה auth.token במשתנה auth.token בכללים שלך. הנה דוגמה לכללים המשתמשים hasEmergencyTowel המותאמת אישית hasEmergencyTowel :

{
  "rules": {
    "frood": {
      // A towel is about the most massively useful thing an interstellar
      // hitchhiker can have
      ".read": "auth.token.hasEmergencyTowel === true"
    }
  }
}

מפתחים שיוצרים אסימוני אימות מותאמים אישית משלהם יכולים להוסיף תביעות לאסימונים אלה. טענות אלה auth.token למשתנה auth.token בכללים שלך.

נתונים קיימים לעומת נתונים חדשים

משתנה data שהוגדר מראש משמש להתייחסות לנתונים לפני שמתרחשת פעולת כתיבה. לעומת זאת, המשתנה newData מכיל את הנתונים החדשים שקיימים אם פעולת הכתיבה תצליח. newData מייצג את התוצאה הממוזגת של הנתונים החדשים שנכתבו והנתונים הקיימים.

לשם המחשה, כלל זה יאפשר לנו ליצור רשומות חדשות או למחוק רשומות קיימות, אך לא לבצע שינויים בנתונים קיימים שאינם null:

// 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()"

הפניה לנתיבים בנתיבים אחרים

ניתן להשתמש בכל נתונים כקריטריון לחוקים. באמצעות המשתנים newData מראש root , data newData , אנו יכולים לגשת לכל נתיב כפי שהיה קיים לפני או אחרי אירוע כתיבה.

שקול דוגמה זו, המאפשרת פעולות כתיבה כל עוד הערך של /allow_writes/ node true , לצומת האב אין סט דגל readOnly , ויש ילד בשם foo בנתונים שנכתבו לאחרונה:

".write": "root.child('allow_writes').val() === true &&
          !data.parent().child('readOnly').exists() &&
          newData.child('foo').exists()"

אימות נתונים

אכיפת מבני נתונים ואימות הפורמט והתוכן של הנתונים צריכים להיעשות באמצעות כללי .validate , המופעלים רק לאחר שכלל .write מצליח להעניק גישה. להלן דוגמה .validate כלל .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 Database. שקול את הכללים הבאים:

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

עם גרסה זו בחשבון, עיין בתוצאות לפעולות הכתיבה הבאות:

JavaScript
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

עכשיו בואו נראה של באותו מבנה, אלא באמצעות .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()"
      }
    }
  }
}

בגרסה זו, כל אחת מהפעולות הבאות תצליח:

JavaScript
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
}

השאילתה הבאה, הכוללת את פרמטרי השאילתה בכלל, תצליח:

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. ביטויים זמינים בכללי אבטחת מסדי נתונים בזמן אמת.

ביטויים של כלל מבוסס שאילתות
ביטוי סוּג תיאור
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 אם לא מוגדרת מגבלה.

הצעדים הבאים

לאחר דיון זה בתנאים, יש לך הבנה מתוחכמת יותר של כללים ואתה מוכן:

למד כיצד לטפל במקרי שימוש מרכזיים ולמד את זרימת העבודה לפיתוח, בדיקה ופריסה של כללים:

למד תכונות של כללים הספציפיים למסד נתונים בזמן אמת: