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

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

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

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

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

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

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

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

משתנה auth

משתנה auth המוגדר מראש בכללים הוא ריק לפני שהאימות מתבצע.

לאחר אימות של משתמש באמצעות Firebase Authentication הוא יכיל את המאפיינים הבאים:

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

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

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

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

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

עבור אפליקציות הדורשות בקרת גישה מותאמת אישית עבור משתמשים שונים, אימות Firebase מאפשר למפתחים להגדיר תביעות על משתמש 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 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);
Objective-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);
Java
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);
Objective-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)
Java
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 אם לא הוגדרה מגבלה.

הצעדים הבאים

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

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

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