Catch up on everthing we announced at this year's Firebase Summit. Learn more

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

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

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

auth משתנה

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

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

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

הצעדים הבאים

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

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

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