การเขียนเงื่อนไขสำหรับกฎความปลอดภัยของ Cloud Firestore

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

โครงสร้างหลักของกฎความปลอดภัยของ Cloud Firestore คือเงื่อนไข เงื่อนไขคือนิพจน์บูลีนที่กำหนดว่าการดำเนินการใดควรได้รับอนุญาตหรือปฏิเสธ ใช้กฎความปลอดภัยเพื่อเขียนเงื่อนไขที่ตรวจสอบการรับรองความถูกต้องของผู้ใช้ ตรวจสอบข้อมูลขาเข้า หรือแม้แต่เข้าถึงส่วนอื่นๆ ของฐานข้อมูลของคุณ

การรับรองความถูกต้อง

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

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to access documents in the "cities" collection
    // only if they are authenticated.
    match /cities/{city} {
      allow read, write: if request.auth != null;
    }
  }
}

รูปแบบทั่วไปอีกประการหนึ่งคือเพื่อให้แน่ใจว่าผู้ใช้สามารถอ่านและเขียนข้อมูลของตนเองได้เท่านั้น:

service cloud.firestore {
  match /databases/{database}/documents {
    // Make sure the uid of the requesting user matches name of the user
    // document. The wildcard expression {userId} makes the userId variable
    // available in rules.
    match /users/{userId} {
      allow read, update, delete: if request.auth != null && request.auth.uid == userId;
      allow create: if request.auth != null;
    }
  }
}

หากแอปของคุณใช้ Firebase Authentication หรือ Google Cloud Identity Platform ตัวแปร request.auth จะมีข้อมูลการตรวจสอบสิทธิ์สำหรับไคลเอ็นต์ที่ร้องขอข้อมูล สำหรับข้อมูลเพิ่มเติมเกี่ยวกับ request.auth โปรดดู เอกสารอ้างอิง

การตรวจสอบข้อมูล

แอพจำนวนมากจัดเก็บข้อมูลการควบคุมการเข้าถึงเป็นช่องในเอกสารในฐานข้อมูล กฎความปลอดภัยของ Cloud Firestore สามารถอนุญาตหรือปฏิเสธการเข้าถึงแบบไดนามิกโดยอิงตามข้อมูลเอกสาร:

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to read data if the document has the 'visibility'
    // field set to 'public'
    match /cities/{city} {
      allow read: if resource.data.visibility == 'public';
    }
  }
}

ตัวแปร resource อ้างอิงถึงเอกสารที่ร้องขอ และ resource.data คือแผนผังของฟิลด์และค่าทั้งหมดที่จัดเก็บไว้ในเอกสาร สำหรับข้อมูลเพิ่มเติมเกี่ยวกับตัวแปร resource โปรดดู เอกสารอ้างอิง

เมื่อเขียนข้อมูล คุณอาจต้องการเปรียบเทียบข้อมูลขาเข้ากับข้อมูลที่มีอยู่ ในกรณีนี้ หากชุดกฎของคุณอนุญาตให้มีการเขียนที่รอดำเนินการ ตัวแปร request.resource จะมีสถานะในอนาคตของเอกสาร สำหรับการดำเนินการ update ที่แก้ไขเฉพาะชุดย่อยของฟิลด์เอกสาร request.resource จะมีสถานะเอกสารที่ค้างอยู่หลังการดำเนินการ คุณสามารถตรวจสอบค่าฟิลด์ใน request.resource เพื่อป้องกันการอัปเดตข้อมูลที่ไม่ต้องการหรือไม่สอดคล้องกัน:

service cloud.firestore {
  match /databases/{database}/documents {
    // Make sure all cities have a positive population and
    // the name is not changed
    match /cities/{city} {
      allow update: if request.resource.data.population > 0
                    && request.resource.data.name == resource.data.name;
    }
  }
}

เข้าถึงเอกสารอื่นๆ

การใช้ฟังก์ชัน get() และ exists() กฎความปลอดภัยของคุณสามารถประเมินคำขอขาเข้าเทียบกับเอกสารอื่นๆ ในฐานข้อมูลได้ ฟังก์ชั่น get() และ exists() ทั้งคู่คาดหวังเส้นทางเอกสารที่ระบุอย่างครบถ้วน เมื่อใช้ตัวแปรเพื่อสร้างเส้นทางสำหรับ get() และ exists() คุณจะต้องหลีกเลี่ยงตัวแปรอย่างชัดเจนโดยใช้ไวยากรณ์ $(variable)

ในตัวอย่างด้านล่าง ตัวแปร database ถูกจับโดยคำสั่งการจับ match /databases/{database}/documents และใช้เพื่อสร้างเส้นทาง:

service cloud.firestore {
  match /databases/{database}/documents {
    match /cities/{city} {
      // Make sure a 'users' document exists for the requesting user before
      // allowing any writes to the 'cities' collection
      allow create: if request.auth != null && exists(/databases/$(database)/documents/users/$(request.auth.uid))

      // Allow the user to delete cities if their user document has the
      // 'admin' field set to 'true'
      allow delete: if request.auth != null && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true
    }
  }
}

สำหรับการเขียน คุณสามารถใช้ฟังก์ชัน getAfter() เพื่อเข้าถึงสถานะของเอกสารหลังจากธุรกรรมหรือชุดการเขียนเสร็จสมบูรณ์ แต่ก่อนที่ธุรกรรมหรือชุดการดำเนินการจะกระทำ เช่นเดียวกับ get() ฟังก์ชัน getAfter() รับเส้นทางเอกสารที่ระบุอย่างครบถ้วน คุณสามารถใช้ getAfter() เพื่อกำหนดชุดการเขียนที่ต้องเกิดขึ้นร่วมกันเป็นธุรกรรมหรือเป็นชุด

เข้าถึงขีดจำกัดการโทร

มีการจำกัดการเรียกเข้าถึงเอกสารต่อการประเมินชุดกฎ:

  • 10 สำหรับคำขอเอกสารเดี่ยวและคำขอแบบสอบถาม
  • 20 สำหรับการอ่านหลายเอกสาร ธุรกรรม และการเขียนแบบเป็นชุด ขีดจำกัดก่อนหน้านี้คือ 10 ยังใช้กับแต่ละการดำเนินการด้วย

    ตัวอย่างเช่น ลองจินตนาการว่าคุณสร้างคำขอเขียนเป็นชุดโดยมีการดำเนินการเขียน 3 ครั้ง และกฎความปลอดภัยของคุณใช้การเรียกเข้าถึงเอกสาร 2 ครั้งเพื่อตรวจสอบความถูกต้องของการเขียนแต่ละครั้ง ในกรณีนี้ การเขียนแต่ละครั้งจะใช้การเรียกเข้าถึง 2 ครั้งจาก 10 ครั้ง และคำขอการเขียนแบบแบตช์ใช้การเรียกการเข้าถึง 6 ครั้งจาก 20 ครั้ง

เกินขีดจำกัดอย่างใดอย่างหนึ่งส่งผลให้เกิดข้อผิดพลาดในการปฏิเสธสิทธิ์ การเรียกเข้าถึงเอกสารบางรายการอาจถูกแคช และการเรียกที่แคชไว้จะไม่นับรวมในขีดจำกัด

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

เข้าถึงการโทรและราคา

การใช้ฟังก์ชันเหล่านี้จะดำเนินการอ่านในฐานข้อมูลของคุณ ซึ่งหมายความว่าคุณจะถูกเรียกเก็บเงินสำหรับการอ่านเอกสาร แม้ว่ากฎของคุณจะปฏิเสธคำขอก็ตาม ดู ราคา Cloud Firestore สำหรับข้อมูลการเรียกเก็บเงินที่เฉพาะเจาะจงมากขึ้น

ฟังก์ชั่นที่กำหนดเอง

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

  • ฟังก์ชันสามารถมีคำสั่ง return ได้เพียงคำสั่งเดียวเท่านั้น ไม่สามารถมีตรรกะเพิ่มเติมใดๆ ได้ ตัวอย่างเช่น ไม่สามารถดำเนินการวนซ้ำหรือเรียกใช้บริการภายนอกได้
  • ฟังก์ชันต่างๆ สามารถเข้าถึงฟังก์ชันและตัวแปรจากขอบเขตที่กำหนดไว้ได้โดยอัตโนมัติ ตัวอย่างเช่น ฟังก์ชันที่กำหนดภายในขอบเขต service cloud.firestore มีสิทธิ์เข้าถึงตัวแปร resource และฟังก์ชันในตัว เช่น get() และ exists()
  • ฟังก์ชันอาจเรียกใช้ฟังก์ชันอื่นแต่อาจไม่เรียกซ้ำ ความลึกของสแต็กการโทรทั้งหมดถูกจำกัดไว้ที่ 10
  • ในกฎเวอร์ชัน v2 ฟังก์ชันสามารถกำหนดตัวแปรโดยใช้คีย์เวิร์ด let ฟังก์ชันสามารถผูก Let ได้สูงสุด 10 รายการ แต่ต้องลงท้ายด้วยคำสั่ง Return

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

service cloud.firestore {
  match /databases/{database}/documents {
    // True if the user is signed in or the requested data is 'public'
    function signedInOrPublic() {
      return request.auth.uid != null || resource.data.visibility == 'public';
    }

    match /cities/{city} {
      allow read, write: if signedInOrPublic();
    }

    match /users/{user} {
      allow read, write: if signedInOrPublic();
    }
  }
}

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

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

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

ตัวอย่างเช่น ใช้กฎความปลอดภัยต่อไปนี้:

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to read data if the document has the 'visibility'
    // field set to 'public'
    match /cities/{city} {
      allow read: if resource.data.visibility == 'public';
    }
  }
}

ปฏิเสธ : กฎนี้ปฏิเสธแบบสอบถามต่อไปนี้เนื่องจากชุดผลลัพธ์สามารถรวมเอกสารที่ visibility ไม่เป็น public :

เว็บ
db.collection("cities").get()
    .then(function(querySnapshot) {
        querySnapshot.forEach(function(doc) {
            console.log(doc.id, " => ", doc.data());
    });
});

อนุญาต : กฎนี้อนุญาตให้มีการสืบค้นต่อไปนี้เนื่องจากส่วนคำสั่ง where("visibility", "==", "public") รับประกันว่าชุดผลลัพธ์จะเป็นไปตามเงื่อนไขของกฎ:

เว็บ
db.collection("cities").where("visibility", "==", "public").get()
    .then(function(querySnapshot) {
        querySnapshot.forEach(function(doc) {
            console.log(doc.id, " => ", doc.data());
        });
    });

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

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