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

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

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

การตรวจสอบสิทธิ์

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

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 หรือ Google Cloud Identity Platform ตัวแปร request.auth จะมีข้อมูลการตรวจสอบสิทธิ์สำหรับไคลเอ็นต์ที่ขอข้อมูล ดูข้อมูลเพิ่มเติมเกี่ยวกับ request.auth ได้ที่เอกสารอ้างอิง

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

แอปจำนวนมากจัดเก็บข้อมูลการควบคุมการเข้าถึงเป็นฟิลด์ในเอกสารในฐานข้อมูล Cloud Firestore Security Rules สามารถอนุญาตหรือปฏิเสธการเข้าถึงแบบไดนามิกตามข้อมูลเอกสาร ได้ดังนี้

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() ทั้ง 2 ฟังก์ชันต้องระบุเส้นทางเอกสารแบบเต็ม เมื่อใช้ตัวแปรเพื่อสร้างเส้นทางสำหรับ 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 ฟังก์ชันมีได้สูงสุด 10 การเชื่อมโยง let แต่ต้องลงท้ายด้วยคำสั่ง return

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

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 กฎความปลอดภัยจะประเมินการค้นหาแต่ละครั้งเทียบกับผลลัพธ์ที่อาจเกิดขึ้น และจะทำให้คำขอไม่สำเร็จหากอาจแสดงเอกสารที่ไคลเอ็นต์ไม่มีสิทธิ์อ่าน การค้นหาต้องเป็นไปตามข้อจำกัดที่กำหนดโดย กฎความปลอดภัย ดูข้อมูลเพิ่มเติมเกี่ยวกับกฎและการค้นหาความปลอดภัยได้ที่การค้นหาข้อมูลอย่างปลอดภัย

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