כללי אבטחה בסיסיים

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

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

כדי לגשת לכללים ולעדכן אותם, פועלים לפי השלבים שמפורטים במאמר ניהול של Firebase Security Rules ופריסה שלו.

כללי ברירת המחדל: מצב נעילה

כשיוצרים מסד נתונים או מופע אחסון במסוף Firebase, בוחרים אם Firebase Security Rules להגביל את הגישה לנתונים (מצב נעול) או לאפשר לכל אחד גישה (מצב בדיקה). ב-Cloud Firestore וב-Realtime Database, הכללים שמוגדרים כברירת מחדל למצב נעול הם דחיית הגישה לכל המשתמשים. ב-Cloud Storage, רק משתמשים מאומתים יכולים לגשת למאגרי האחסון.

Cloud Firestore

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if false;
    }
  }
}

Realtime Database

{
  "rules": {
    ".read": false,
    ".write": false
  }
}

Cloud Storage

service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read, write: if false;
    }
  }
}

כללים של סביבת פיתוח

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

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

כל המשתמשים המאומתים

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

Cloud Firestore

service cloud.firestore {
  match /databases/{database}/documents {
    match /some_collection/{document} {
      allow read, write: if request.auth != null;
    }
  }
}

Realtime Database

{
  "rules": {
    "some_path": {
      ".read": "auth.uid !== null",
      ".write": "auth.uid !== null"
    }
  }
}

Cloud Storage

service firebase.storage {
  match /b/{bucket}/o {
    match /some_folder/{fileName} {
      allow read, write: if request.auth != null;
    }
  }
}

כללים שמוכנים לייצור

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

מומלץ לכתוב כללים כשמבנים את הנתונים, כי האופן שבו מגדירים את הכללים משפיע על האופן שבו מגבילים את הגישה לנתונים בנתיבים שונים.

גישה רק לבעלי התוכן

הכללים האלה מגבילים את הגישה לבעלים המאומת של התוכן בלבד. רק משתמש אחד יכול לקרוא ולכתוב את הנתונים, ונתיב הנתונים מכיל את מזהה המשתמש.

מתי הכלל הזה מתאים: הכלל הזה מתאים אם הנתונים מבודדים לפי משתמש – אם המשתמש היחיד שצריך לגשת לנתונים הוא אותו משתמש שיצר את הנתונים.

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

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

Cloud Firestore

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow only authenticated content owners access
    match /some_collection/{userId}/{document} {
      allow read, write: if request.auth != null && request.auth.uid == userId
    }
  }
}

Realtime Database

{
  "rules": {
    "some_path": {
      "$uid": {
        // Allow only authenticated content owners access to their data
        ".read": "auth !== null && auth.uid === $uid",
        ".write": "auth !== null && auth.uid === $uid"
      }
    }
  }
}

Cloud Storage

// Grants a user access to a node matching their user ID
service firebase.storage {
  match /b/{bucket}/o {
    // Files look like: "user/<UID>/file.txt"
    match /user/{userId}/{fileName} {
      allow read, write: if request.auth != null && request.auth.uid == userId;
    }
  }
}

גישה מעורבת ציבורית ופרטית

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

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

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

כדי להגדיר את הכלל הזה: יוצרים כלל שמאפשר גישת קריאה לכל המשתמשים (או לכל המשתמשים המאומתים), ומוודאים שהמשתמש שכותב נתונים הוא הבעלים.

Cloud Firestore

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow public read access, but only content owners can write
    match /some_collection/{document} {
      // Allow public reads
      allow read: if true
      // Allow creation if the current user owns the new document
      allow create: if request.auth.uid == request.resource.data.author_uid;
      // Allow updates by the owner, and prevent change of ownership
      allow update: if request.auth.uid == request.resource.data.author_uid
                    && request.auth.uid == resource.data.author_uid;
      // Allow deletion if the current user owns the existing document
      allow delete: if request.auth.uid == resource.data.author_uid;
    }
  }
}

Realtime Database

{
// Allow anyone to read data, but only authenticated content owners can
// make changes to their data

  "rules": {
    "some_path": {
      "$uid": {
        ".read": true,
        // or ".read": "auth.uid !== null" for only authenticated users
        ".write": "auth.uid === $uid"
      }
    }
  }
}

Cloud Storage

service firebase.storage {
  match /b/{bucket}/o {
    // Files look like: "user/<UID>/file.txt"
    match /user/{userId}/{fileName} {
      allow read;
      allow write: if request.auth.uid == userId;
    }
  }
}

גישה מבוססת-מאפיינים וגישה מבוססת-תפקידים

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

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

מתי הכלל הזה לא פועל: ב-Realtime Database וב-Cloud Storage, הכללים לא יכולים להשתמש בשיטה get() שכללי Cloud Firestore יכולים לשלב. לכן, אתם צריכים לבנות את המטא-נתונים של מסד הנתונים או הקובץ כך שישקפו את המאפיינים שבהם אתם משתמשים בכללים.

כדי להגדיר את הכלל הזה: ב-Cloud Firestore, כוללים שדה במסמכים של המשתמשים שאפשר לקרוא, ואז יוצרים את הכלל כך שיקרא את השדה הזה ויעניק גישה על סמך תנאי. ב-Realtime Database, יוצרים נתיב נתונים שמגדיר את המשתמשים באפליקציה ומקצה להם תפקיד בצומת צאצא.

אפשר גם להגדיר טענות בהתאמה אישית ב-Authentication ואז לאחזר את המידע הזה מהמשתנה auth.token בכל Firebase Security Rules.

מאפיינים ותפקידים שמוגדרים על סמך נתונים

הכללים האלה פועלים רק ב-Cloud Firestore וב-Realtime Database.

Cloud Firestore

חשוב לזכור שבכל פעם שהכללים כוללים קריאה, כמו הכללים שבהמשך, אתם מחויבים על פעולת קריאה ב-Cloud Firestore.

service cloud.firestore {
  match /databases/{database}/documents {
    // For attribute-based access control, Check a boolean `admin` attribute
    allow write: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true;
    allow read: true;

    // Alterntatively, for role-based access, assign specific roles to users
    match /some_collection/{document} {
     allow read: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == "Reader"
     allow write: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == "Writer"
   }
  }
}

Realtime Database

{
  "rules": {
    "some_path": {
      "${subpath}": {
        //
        ".write": "root.child('users').child(auth.uid).child('role').val() === 'admin'",
        ".read": true
      }
    }
  }
}

מאפיינים ותפקידים של טענות מותאמות אישית

כדי להטמיע את הכללים האלה, צריך להגדיר טענות מותאמות אישית ב-Firebase Authentication ואז להשתמש בטענות בכללים.

Cloud Firestore

service cloud.firestore {
  match /databases/{database}/documents {
    // For attribute-based access control, check for an administrator claim
    allow write: if request.auth.token.admin == true;
    allow read: true;

    // Alterntatively, for role-based access, assign specific roles to users
    match /some_collection/{document} {
     allow read: if request.auth.token.reader == "true";
     allow write: if request.auth.token.writer == "true";
   }
  }
}

Realtime Database

{
  "rules": {
    "some_path": {
      "$uid": {
        // Create a custom claim for each role or group
        // you want to use
        ".write": "auth.uid !== null && auth.token.writer === true",
        ".read": "auth.uid !== null && auth.token.reader === true"
      }
    }
  }
}

Cloud Storage

service firebase.storage {
  // Allow reads if the group ID in your token matches the file metadata's `owner` property
  // Allow writes if the group ID is in the user's custom token
  match /files/{groupId}/{fileName} {
    allow read: if resource.metadata.owner == request.auth.token.groupId;
    allow write: if request.auth.token.groupId == groupId;
  }
}

מאפייני הדיירות

כדי להטמיע את הכללים האלה, צריך להגדיר ריבוי דיירים ב-Google Cloud Identity Platform ‏ (GCIP) ואז להשתמש בדייר בכללים. בדוגמאות הבאות מוצגות הרשאות כתיבה למשתמש בדייר ספציפי, למשל, tenant2-m6tyz

Cloud Firestore

service cloud.firestore {
  match /databases/{database}/documents {
    // For tenant-based access control, check for a tenantID
    allow write: if request.auth.token.firebase.tenant == 'tenant2-m6tyz';
    allow read: true;
  }
}

Realtime Database

{
  "rules": {
    "some_path": {
      "$uid": {
        // Only allow reads and writes if user belongs to a specific tenant
        ".write": "auth.uid !== null && auth.token.firebase.tenant === 'tenant2-m6tyz'",
        ".read": "auth.uid !== null
      }
    }
  }
}

Cloud Storage

service firebase.storage {
  // Only allow reads and writes if user belongs to a specific tenant
  match /files/{tenantId}/{fileName} {
    allow read: if request.auth != null;
    allow write: if request.auth.token.firebase.tenant == tenantId;
  }
}