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

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

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

หากต้องการความช่วยเหลือจาก AI ให้คัดลอกพรอมต์ด้านล่างลงในตัวแทนการเขียนโค้ด AI (เช่น Gemini ใน Android Studio, Antigravity, Claude Code, Codex และ Cursor) คุณสามารถปรับแต่งพรอมต์และทำซ้ำกับ Agent ได้ ดูข้อมูลการติดตั้งโดยละเอียดได้ที่ทักษะเอเจนต์ Firebase

Help me use Firebase in my app.
1. Make sure that **Firebase agent skills** are installed and updated. For Claude Code, Codex, or Cursor agents, help me install the Firebase plugin. For other agents, run `npx skills add firebase/agent-skills`. Always try to use **Firebase agent skills** for Firebase-related tasks.
2. Help me write, validate, and deploy Firebase Security Rules to protect my app's Firestore data.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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());
        });
    });

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

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