เรียนรู้ไวยากรณ์หลักของภาษากฎความปลอดภัยของฐานข้อมูลเรียลไทม์

จัดทุกอย่างให้เป็นระเบียบอยู่เสมอด้วยคอลเล็กชัน บันทึกและจัดหมวดหมู่เนื้อหาตามค่ากำหนดของคุณ

กฎความปลอดภัยของฐานข้อมูลเรียลไทม์ของ Firebase ช่วยให้คุณควบคุมการเข้าถึงข้อมูลที่จัดเก็บไว้ในฐานข้อมูลของคุณได้ ไวยากรณ์กฎที่ยืดหยุ่นช่วยให้คุณสร้างกฎที่ตรงกับอะไรก็ได้ ตั้งแต่การเขียนทั้งหมดไปยังฐานข้อมูลของคุณไปจนถึงการดำเนินการบนแต่ละโหนด

กฎความปลอดภัยของฐานข้อมูลแบบเรียลไทม์คือการกำหนดค่าที่ เปิดเผย สำหรับฐานข้อมูลของคุณ ซึ่งหมายความว่ากฎถูกกำหนดแยกจากตรรกะของผลิตภัณฑ์ มีข้อดีหลายประการ: ลูกค้าไม่รับผิดชอบต่อการบังคับใช้ความปลอดภัย การใช้งานแบบบั๊กกี้จะไม่กระทบต่อข้อมูลของคุณ และที่สำคัญที่สุด ไม่จำเป็นต้องมีผู้ตัดสินระดับกลาง เช่น เซิร์ฟเวอร์ เพื่อปกป้องข้อมูลจากโลก

หัวข้อนี้อธิบายเกี่ยวกับไวยากรณ์พื้นฐานและโครงสร้าง Realtime Database Security Rules ที่ใช้เพื่อสร้างชุดกฎที่สมบูรณ์

การวางโครงสร้างกฎความปลอดภัยของคุณ

กฎความปลอดภัยของฐานข้อมูลแบบเรียลไทม์ประกอบด้วยนิพจน์ที่เหมือน JavaScript ที่มีอยู่ในเอกสาร JSON โครงสร้างของกฎของคุณควรเป็นไปตามโครงสร้างของข้อมูลที่คุณจัดเก็บไว้ในฐานข้อมูลของคุณ

กฎพื้นฐาน ระบุชุดของโหนด ที่จะรักษาความปลอดภัย วิธีการเข้าถึง (เช่น อ่าน เขียน) ที่เกี่ยวข้อง และ เงื่อนไข ที่อนุญาตหรือปฏิเสธการเข้าถึง ในตัวอย่างต่อไปนี้ เงื่อนไข ของเราจะเป็นข้อความ true และ false อย่างง่าย แต่ในหัวข้อถัดไป เราจะพูดถึงวิธีการแสดงเงื่อนไขแบบไดนามิกมากขึ้น

ตัวอย่างเช่น หากเรากำลังพยายามรักษาความปลอดภัย child_node ภายใต้ parent_node ไวยากรณ์ทั่วไปที่ต้องปฏิบัติตามคือ:

{
  "rules": {
    "parent_node": {
      "child_node": {
        ".read": <condition>,
        ".write": <condition>,
        ".validate": <condition>,
      }
    }
  }
}

ลองใช้รูปแบบนี้ ตัวอย่างเช่น สมมติว่าคุณกำลังติดตามรายการข้อความและมีข้อมูลที่มีลักษณะดังนี้:

{
  "messages": {
    "message0": {
      "content": "Hello",
      "timestamp": 1405704370369
    },
    "message1": {
      "content": "Goodbye",
      "timestamp": 1405704395231
    },
    ...
  }
}

กฎของคุณควรมีโครงสร้างในลักษณะเดียวกัน ต่อไปนี้คือชุดกฎสำหรับการรักษาความปลอดภัยแบบอ่านอย่างเดียวที่อาจเหมาะสมสำหรับโครงสร้างข้อมูลนี้ ตัวอย่างนี้แสดงให้เห็นว่าเราระบุโหนดฐานข้อมูลที่กฎใช้และเงื่อนไขสำหรับการประเมินกฎที่โหนดเหล่านั้นอย่างไร

{
  "rules": {
    // For requests to access the 'messages' node...
    "messages": {
      // ...and the individual wildcarded 'message' nodes beneath
      // (we'll cover wildcarding variables more a bit later)....
      "$message": {

        // For each message, allow a read operation if <condition>. In this
        // case, we specify our condition as "true", so read access is always granted.
        ".read": "true",

        // For read-only behavior, we specify that for write operations, our
        // condition is false.
        ".write": "false"
      }
    }
  }
}

การดำเนินงานกฎพื้นฐาน

มีกฎสามประเภทสำหรับการบังคับใช้ความปลอดภัยตามประเภทของการดำเนินการที่ดำเนินการกับข้อมูล: .write , .read และ . .validate นี่คือบทสรุปโดยย่อของวัตถุประสงค์ของพวกเขา:

ประเภทของกฎ
.อ่าน อธิบายว่าผู้ใช้อนุญาตให้อ่านข้อมูลหรือไม่และเมื่อใด
.เขียน อธิบายว่าข้อมูลสามารถเขียนได้หรือไม่และเมื่อใด
.ตรวจสอบความถูกต้อง กำหนดว่าค่าที่จัดรูปแบบอย่างถูกต้องจะมีลักษณะอย่างไร ไม่ว่าจะมีแอตทริบิวต์ย่อยและชนิดข้อมูลหรือไม่

ตัวแปรการจับสัญลักษณ์แทน

คำสั่งกฎทั้งหมดชี้ไปที่โหนด คำสั่งสามารถชี้ไปที่โหนดใดโหนดหนึ่งหรือใช้ ตัวแปรการจับ สัญลักษณ์แทน $ เพื่อชี้ไปยังชุดของโหนดที่ระดับของลำดับชั้น ใช้ตัวแปรการดักจับเหล่านี้เพื่อเก็บค่าของคีย์โหนดเพื่อใช้ภายในคำสั่งกฎที่ตามมา เทคนิคนี้ช่วยให้คุณเขียน เงื่อนไข กฎที่ซับซ้อนมากขึ้น ซึ่งเราจะพูดถึงรายละเอียดเพิ่มเติมในหัวข้อถัดไป

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

อ่านและเขียนกฎ Cascade

กฎ . .read และ .write ทำงานจากบนลงล่าง โดยมีกฎที่ตื้นกว่าซึ่งจะแทนที่กฎที่ลึกกว่า หากกฎให้สิทธิ์ในการอ่านหรือเขียนในพาธใดพาธหนึ่ง กฎนั้นก็จะให้สิทธิ์เข้าถึง โหนดย่อยทั้งหมดที่อยู่ภายใต้นั้นด้วย พิจารณาโครงสร้างต่อไปนี้:

{
  "rules": {
     "foo": {
        // allows read to /foo/*
        ".read": "data.child('baz').val() === true",
        "bar": {
          /* ignored, since read was allowed already */
          ".read": false
        }
     }
  }
}

โครงสร้างความปลอดภัยนี้อนุญาตให้อ่าน /bar/ bar/ เมื่อใดก็ตามที่ /foo/ มี child baz ที่มีค่า true ".read": false ภายใต้ /foo/bar/ ไม่มีผลที่นี่ เนื่องจากการเข้าถึงไม่สามารถเพิกถอนได้โดยเส้นทางย่อย

แม้ว่าจะดูไม่เป็นธรรมชาติในทันที แต่นี่เป็นส่วนที่มีประสิทธิภาพของภาษาของกฎและช่วยให้สามารถใช้สิทธิ์การเข้าถึงที่ซับซ้อนมากได้โดยใช้ความพยายามเพียงเล็กน้อย สิ่งนี้จะแสดงให้เห็นเมื่อเราเข้าสู่ความปลอดภัยตามผู้ใช้ในคู่มือนี้ในภายหลัง

โปรดทราบว่ากฎ .validate จะไม่เรียงต่อกัน กฎการตรวจสอบความถูกต้องทั้งหมดต้องเป็นไปตามลำดับชั้นทุกระดับเพื่อให้สามารถเขียนได้

กฎไม่ใช่ตัวกรอง

กฎถูกนำไปใช้ในลักษณะปรมาณู ซึ่งหมายความว่าการดำเนินการอ่านหรือเขียนจะล้มเหลวทันทีหากไม่มีกฎที่ตำแหน่งนั้นหรือที่ตำแหน่งหลักที่ให้สิทธิ์การเข้าถึง แม้ว่าทุกเส้นทางย่อยที่ได้รับผลกระทบจะสามารถเข้าถึงได้ การอ่านที่ตำแหน่งหลักจะล้มเหลวโดยสิ้นเชิง พิจารณาโครงสร้างนี้:

{
  "rules": {
    "records": {
      "rec1": {
        ".read": true
      },
      "rec2": {
        ".read": false
      }
    }
  }
}

หากไม่เข้าใจว่ามีการประเมินกฎแบบปรมาณู ดูเหมือนว่าการดึงข้อมูลเส้นทาง /records/ จะส่งคืน rec1 แต่ไม่ใช่ rec2 อย่างไรก็ตาม ผลลัพธ์ที่แท้จริงนั้นเป็นข้อผิดพลาด:

JavaScript
var db = firebase.database();
db.ref("records").once("value", function(snap) {
  // success method is not called
}, function(err) {
  // error callback triggered with PERMISSION_DENIED
});
วัตถุประสงค์-C
หมายเหตุ: ผลิตภัณฑ์ Firebase นี้ไม่มีในเป้าหมาย App Clip
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
[[_ref child:@"records"] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
  // success block is not called
} withCancelBlock:^(NSError * _Nonnull error) {
  // cancel block triggered with PERMISSION_DENIED
}];
Swift
หมายเหตุ: ผลิตภัณฑ์ Firebase นี้ไม่มีในเป้าหมาย App Clip
var ref = FIRDatabase.database().reference()
ref.child("records").observeSingleEventOfType(.Value, withBlock: { snapshot in
    // success block is not called
}, withCancelBlock: { error in
    // cancel block triggered with PERMISSION_DENIED
})
Java
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference("records");
ref.addListenerForSingleValueEvent(new ValueEventListener() {
  @Override
  public void onDataChange(DataSnapshot snapshot) {
    // success method is not called
  }

  @Override
  public void onCancelled(FirebaseError firebaseError) {
    // error callback triggered with PERMISSION_DENIED
  });
});
พักผ่อน
curl https://docs-examples.firebaseio.com/rest/records/
# response returns a PERMISSION_DENIED error

เนื่องจากการดำเนินการอ่านที่ /records/ เป็นอะตอม และไม่มีกฎการอ่านที่ให้สิทธิ์การเข้าถึงข้อมูลทั้งหมดภายใต้ /records/ การดำเนินการนี้จะทำให้เกิดข้อผิดพลาด PERMISSION_DENIED หากเราประเมินกฎนี้ในโปรแกรมจำลองความปลอดภัยใน คอนโซล Firebase เราจะเห็นว่าการดำเนินการอ่านถูกปฏิเสธเนื่องจากกฎการอ่านไม่อนุญาตให้เข้าถึงเส้นทาง /records/ อย่างไรก็ตาม โปรดทราบว่ากฎสำหรับ rec1 ไม่เคยได้รับการประเมินเนื่องจากไม่อยู่ในเส้นทางที่เราขอ ในการดึงข้อมูล rec1 เราจะต้องเข้าถึงโดยตรง:

JavaScript
var db = firebase.database();
db.ref("records/rec1").once("value", function(snap) {
  // SUCCESS!
}, function(err) {
  // error callback is not called
});
วัตถุประสงค์-C
หมายเหตุ: ผลิตภัณฑ์ Firebase นี้ไม่มีในเป้าหมาย App Clip
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
[[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
    // SUCCESS!
}];
Swift
หมายเหตุ: ผลิตภัณฑ์ Firebase นี้ไม่มีในเป้าหมาย App Clip
var ref = FIRDatabase.database().reference()
ref.child("records/rec1").observeSingleEventOfType(.Value, withBlock: { snapshot in
    // SUCCESS!
})
Java
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference("records/rec1");
ref.addListenerForSingleValueEvent(new ValueEventListener() {
  @Override
  public void onDataChange(DataSnapshot snapshot) {
    // SUCCESS!
  }

  @Override
  public void onCancelled(FirebaseError firebaseError) {
    // error callback is not called
  }
});
พักผ่อน
curl https://docs-examples.firebaseio.com/rest/records/rec1
# SUCCESS!

คำชี้แจงที่ทับซ้อนกัน

เป็นไปได้มากกว่าหนึ่งกฎที่จะนำไปใช้กับโหนด ในกรณีที่นิพจน์กฎหลายรายการระบุโหนด วิธีการเข้าถึงจะถูกปฏิเสธหากมีเงื่อนไข ใด ๆ เป็น false :

{
  "rules": {
    "messages": {
      // A rule expression that applies to all nodes in the 'messages' node
      "$message": {
        ".read": "true",
        ".write": "true"
      },
      // A second rule expression applying specifically to the 'message1` node
      "message1": {
        ".read": "false",
        ".write": "false"
      }
    }
  }
}

ในตัวอย่างข้างต้น การอ่านโหนด message1 จะถูกปฏิเสธเนื่องจากกฎที่สองเป็น false เสมอ แม้ว่ากฎข้อแรกจะเป็น true เสมอ

ขั้นตอนถัดไป

คุณสามารถทำความเข้าใจกฎความปลอดภัยของฐานข้อมูลเรียลไทม์ของ Firebase ได้อย่างลึกซึ้งยิ่งขึ้น:

  • เรียนรู้แนวคิดหลักถัดไปของภาษาของกฎ เงื่อนไข แบบไดนามิก ซึ่งช่วยให้กฎของคุณตรวจสอบการให้สิทธิ์ผู้ใช้ เปรียบเทียบข้อมูลที่มีอยู่และที่เข้ามา ตรวจสอบความถูกต้องของข้อมูลที่เข้ามา ตรวจสอบโครงสร้างของการสืบค้นที่มาจากไคลเอนต์ และอื่นๆ

  • ตรวจสอบกรณีการใช้งานการรักษาความปลอดภัยทั่วไปและ คำจำกัดความกฎความปลอดภัยของ Firebase ที่ กล่าวถึง