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

ขีดจำกัดการเรียกใช้การเข้าถึง

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

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

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

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

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

การเรียกใช้การเข้าถึงและการกำหนดราคา

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

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

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

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

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