กฎการรักษาความปลอดภัยของฐานข้อมูลแบบเรียลไทม์ของ Firebase ช่วยให้คุณควบคุมการเข้าถึงข้อมูลที่จัดเก็บไว้ในฐานข้อมูลของคุณได้ ไวยากรณ์กฎที่ยืดหยุ่นช่วยให้คุณสร้างกฎที่ตรงกับอะไรก็ได้ ตั้งแต่การเขียนทั้งหมดไปยังฐานข้อมูลของคุณไปจนถึงการดำเนินการในแต่ละโหนด
กฎการรักษาความปลอดภัยของฐานข้อมูลแบบเรียลไทม์คือการกำหนดค่า ที่เปิดเผย สำหรับฐานข้อมูลของคุณ ซึ่งหมายความว่ากฎถูกกำหนดแยกต่างหากจากตรรกะของผลิตภัณฑ์ สิ่งนี้มีข้อดีหลายประการ: ลูกค้าไม่มีส่วนรับผิดชอบในการบังคับใช้ความปลอดภัย การใช้งานแบบบั๊กกี้จะไม่ทำให้ข้อมูลของคุณเสียหาย และที่สำคัญที่สุดคือ ไม่จำเป็นต้องมีผู้ตัดสินที่เป็นสื่อกลาง เช่น เซิร์ฟเวอร์ เพื่อปกป้องข้อมูลจากทั่วโลก
หัวข้อนี้จะอธิบายไวยากรณ์และโครงสร้างพื้นฐานกฎการรักษาความปลอดภัยของฐานข้อมูลแบบเรียลไทม์ที่ใช้ในการสร้างชุดกฎที่สมบูรณ์
การจัดโครงสร้างกฎความปลอดภัยของคุณ
กฎการรักษาความปลอดภัยของฐานข้อมูลแบบเรียลไทม์ประกอบด้วยนิพจน์ที่คล้ายกับ 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
ต่อไปนี้เป็นข้อมูลสรุปโดยย่อเกี่ยวกับวัตถุประสงค์:
ประเภทกฎ | |
---|---|
.อ่าน | อธิบายว่าผู้ใช้จะอนุญาตให้อ่านข้อมูลได้หรือไม่และเมื่อใด |
.เขียน | อธิบายว่าจะอนุญาตให้เขียนข้อมูลได้หรือไม่และเมื่อใด |
.ตรวจสอบความถูกต้อง | กำหนดค่าที่จัดรูปแบบอย่างถูกต้องจะมีลักษณะอย่างไร มีแอตทริบิวต์ย่อยหรือไม่ และประเภทข้อมูล |
ตัวแปรการจับไวด์การ์ด
คำสั่งกฎทั้งหมดชี้ไปที่โหนด คำสั่งสามารถชี้ไปที่โหนดเฉพาะหรือใช้ ตัวแปรการจับ wildcard $
เพื่อชี้ไปที่ชุดของโหนดในระดับของลำดับชั้น ใช้ตัวแปรการจับเหล่านี้เพื่อจัดเก็บค่าของคีย์โหนดเพื่อใช้ภายในคำสั่งกฎที่ตามมา เทคนิคนี้ช่วยให้คุณเขียน เงื่อนไข กฎที่ซับซ้อนมากขึ้น ซึ่งเราจะกล่าวถึงรายละเอียดเพิ่มเติมในหัวข้อถัดไป
{ "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/
เมื่อใดก็ตามที่ /foo/
มี child baz
ที่มีค่า true
".read": false
ภายใต้ /foo/bar/
ไม่มีผลกระทบที่นี่ เนื่องจากการเข้าถึงไม่สามารถเพิกถอนได้โดยเส้นทางลูก
แม้ว่าอาจดูไม่เป็นธรรมชาติในทันที แต่นี่เป็นส่วนที่มีประสิทธิภาพของภาษากฎ และช่วยให้สิทธิ์การเข้าถึงที่ซับซ้อนมากสามารถนำไปใช้ได้โดยใช้ความพยายามเพียงเล็กน้อย ข้อมูลนี้จะแสดงเมื่อเราเข้าสู่การรักษาความปลอดภัยตามผู้ใช้ในคู่มือนี้
โปรดทราบว่ากฎ .validate
จะไม่เรียงซ้อน กฎการตรวจสอบทั้งหมดจะต้องเป็นไปตามลำดับชั้นทุกระดับเพื่อให้สามารถเขียนได้
กฎไม่ใช่ตัวกรอง
กฎเกณฑ์ถูกนำมาใช้ในลักษณะอะตอมมิก นั่นหมายความว่าการดำเนินการอ่านหรือเขียนจะล้มเหลวทันที หากไม่มีกฎที่ตำแหน่งนั้นหรือที่ตำแหน่งหลักที่ให้สิทธิ์การเข้าถึง แม้ว่าทุกเส้นทางลูกที่ได้รับผลกระทบจะสามารถเข้าถึงได้ แต่การอ่านที่ตำแหน่งหลักจะล้มเหลวโดยสิ้นเชิง พิจารณาโครงสร้างนี้:
{ "rules": { "records": { "rec1": { ".read": true }, "rec2": { ".read": false } } } }
หากไม่เข้าใจว่ากฎได้รับการประเมินแบบอะตอมมิก อาจดูเหมือนว่าการดึงข้อมูลเส้นทาง /records/
จะส่งกลับ rec1
แต่ไม่ใช่ rec2
อย่างไรก็ตาม ผลลัพธ์ที่แท้จริงคือข้อผิดพลาด:
จาวาสคริปต์
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
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 }];
สวิฟท์
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 })
ชวา
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
เราจะต้องเข้าถึงโดยตรง:
จาวาสคริปต์
var db = firebase.database(); db.ref("records/rec1").once("value", function(snap) { // SUCCESS! }, function(err) { // error callback is not called });
วัตถุประสงค์-C
FIRDatabaseReference *ref = [[FIRDatabase database] reference]; [[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) { // SUCCESS! }];
สวิฟท์
var ref = FIRDatabase.database().reference() ref.child("records/rec1").observeSingleEventOfType(.Value, withBlock: { snapshot in // SUCCESS! })
ชวา
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 ที่จัดการกับสิ่งเหล่านี้