กฎความปลอดภัยของฐานข้อมูลเรียลไทม์ของ Firebase ช่วยให้คุณควบคุมการเข้าถึงข้อมูลที่จัดเก็บในฐานข้อมูลได้ รูปแบบคำสั่งของกฎที่ยืดหยุ่นช่วยให้คุณสร้างกฎที่ตรงกับทุกสิ่งได้ ตั้งแต่การเขียนทั้งหมดไปยังฐานข้อมูลไปจนถึงการดำเนินการในโหนดแต่ละโหนด
กฎความปลอดภัยของ Realtime Database คือการกำหนดค่าแบบประกาศสำหรับฐานข้อมูล ซึ่งหมายความว่ากฎจะกำหนดแยกจากตรรกะผลิตภัณฑ์ ซึ่งมีข้อดีหลายประการ เช่น ไคลเอ็นต์ไม่ต้องรับผิดชอบในการรักษาความปลอดภัย การติดตั้งใช้งานที่มีข้อบกพร่องจะไม่ทำให้ข้อมูลของคุณถูกบุกรุก และที่สำคัญที่สุดคือไม่จำเป็นต้องมีสื่อกลางกลาง เช่น เซิร์ฟเวอร์ เพื่อปกป้องข้อมูลจากโลกภายนอก
หัวข้อนี้อธิบายไวยากรณ์พื้นฐานและโครงสร้างของกฎความปลอดภัยของ Realtime Database ที่ใช้สร้างชุดกฎที่สมบูรณ์
การจัดโครงสร้างกฎความปลอดภัย
กฎความปลอดภัยของ Realtime Database ประกอบด้วยนิพจน์ที่คล้ายกับ 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" } } } }
การดำเนินการกับกฎพื้นฐาน
กฎสำหรับการบังคับใช้ความปลอดภัยมี 3 ประเภทตามประเภทการดำเนินการกับข้อมูล ได้แก่ .write
, .read
และ .validate
ต่อไปนี้เป็นข้อมูลสรุปสั้นๆ เกี่ยวกับวัตถุประสงค์ของฟีเจอร์
ประเภทของกฎ | |
---|---|
.read | อธิบายว่าผู้ใช้จะอ่านข้อมูลได้หรือไม่และเมื่อใด |
.write | อธิบายว่าระบบอนุญาตให้เขียนข้อมูลหรือไม่และเมื่อใด |
.validate | กำหนดลักษณะของค่าที่มีรูปแบบถูกต้อง ไม่ว่าจะมีแอตทริบิวต์ย่อยหรือไม่ และประเภทข้อมูล |
ตัวแปรการจับคู่ไวลด์การ์ด
คำสั่งกฎทั้งหมดชี้ไปยังโหนด คำสั่งสามารถชี้ไปยังโหนดที่เฉพาะเจาะจงหรือใช้$
ไวลด์การ์ดตัวแปรการจับเพื่อชี้ไปยังชุดโหนดที่ระดับลําดับชั้น ใช้ตัวแปรการบันทึกเหล่านี้เพื่อจัดเก็บค่าของคีย์โหนดเพื่อใช้ในคำสั่งกฎต่อๆ ไป เทคนิคนี้ช่วยให้คุณเขียนRules เงื่อนไขที่ซับซ้อนมากขึ้นได้ ซึ่งเราจะอธิบายอย่างละเอียดในหัวข้อถัดไป
{ "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 } } } }
อ่านและเขียนกฎตามลําดับชั้น
กฎ .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/
มี 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 });
Objective-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 }];
Swift
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 }); });
REST
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 });
Objective-C
FIRDatabaseReference *ref = [[FIRDatabase database] reference]; [[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) { // SUCCESS! }];
Swift
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 } });
REST
curl https://docs-examples.firebaseio.com/rest/records/rec1 # SUCCESS!
ข้อความที่ทับซ้อนกัน
กฎอาจมีผลกับโหนดได้มากกว่า 1 รายการ ในกรณีที่นิพจน์กฎหลายรายการระบุโหนด ระบบจะปฏิเสธวิธีการเข้าถึงหากเงื่อนไขใดข้อหนึ่งเป็น 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
จะไม่ได้รับอนุญาตเนื่องจากกฎที่ 2 เป็น false
เสมอ แม้ว่ากฎแรกจะเป็น true
เสมอก็ตาม
ขั้นตอนถัดไป
คุณทําความเข้าใจกฎการรักษาความปลอดภัยของ Firebase Realtime Database ได้มากขึ้นโดยทำดังนี้
ดูแนวคิดหลักถัดไปของRulesภาษา ซึ่งก็คือเงื่อนไขแบบไดนามิก ซึ่งจะช่วยให้ Rules ตรวจสอบการให้สิทธิ์ของผู้ใช้ เปรียบเทียบข้อมูลที่มีอยู่และข้อมูลที่เข้ามา ตรวจสอบข้อมูลที่เข้ามา ตรวจสอบโครงสร้างของคําค้นหาที่มาจากไคลเอ็นต์ และอื่นๆ
ตรวจสอบกรณีการใช้งานด้านความปลอดภัยทั่วไปและคำจำกัดความของกฎการรักษาความปลอดภัยของ Firebase ที่จัดการกับกรณีการใช้งานเหล่านั้น