Join us for Firebase Summit on November 10, 2021. Tune in to learn how Firebase can help you accelerate app development, release with confidence, and scale with ease. Register

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

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

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

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

auth משתנה

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

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

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

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

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
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);
Objective-C
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.orderByKey
query.orderByPriority
query.orderByValue
בוליאני נכון לשאילתות המוזמנות לפי מפתח, עדיפות או ערך. שקר אחרת.
query.orderByChild חוּט
ריק
השתמש במחרוזת כדי לייצג את הנתיב היחסי לצומת ילדים. לדוגמה, query.orderByChild == "address/zip" . אם השאילתה אינה מסודרת על ידי צומת ילדים, ערך זה הוא אפס.
query.startAt
query.endAt
query.equalTo
חוּט
מספר
בוליאני
ריק
אחזור גבולות שאילתת הביצוע, או מחזיר null אם אין קבוצה מוגבלת.
query.limitToFirst
query.limitToLast
מספר
ריק
אחזור המגבלה על שאילתת הביצוע, או מחזיר null אם אין הגבלה.

הצעדים הבאים

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

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

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