ค้นหาข้อมูลอย่างปลอดภัย

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

หากต้องการความช่วยเหลือจาก 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.

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

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

การค้นหาและกฎความปลอดภัย

ดังที่ตัวอย่างด้านล่างแสดงให้เห็น คุณต้องเขียนการค้นหาให้สอดคล้องกับข้อจำกัดของกฎความปลอดภัย

รักษาความปลอดภัยและค้นหาเอกสารตามauth.uid

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

/stories/{storyid}

{
  title: "A Great Story",
  content: "Once upon a time...",
  author: "some_auth_id",
  published: false
}

นอกจากช่อง title และ content แล้ว เอกสารแต่ละฉบับยังจัดเก็บช่อง author และ published เพื่อใช้ในการควบคุมการเข้าถึงด้วย ตัวอย่างเหล่านี้ถือว่าแอปใช้การตรวจสอบสิทธิ์ Firebase เพื่อตั้งค่าช่อง author เป็น UID ของผู้ใช้ที่สร้างเอกสาร นอกจากนี้ Firebase Authentication ยังป้อนข้อมูลตัวแปร request.auth ในกฎความปลอดภัยด้วย

กฎความปลอดภัยต่อไปนี้ใช้ตัวแปร request.auth และ resource.data เพื่อจำกัดสิทธิ์การอ่านและเขียนสำหรับ story แต่ละรายการให้กับผู้เขียน

service cloud.firestore {
  match /databases/{database}/documents {
    match /stories/{storyid} {
      // Only the authenticated user who authored the document can read or write
      allow read, write: if request.auth != null && request.auth.uid == resource.data.author;
    }
  }
}

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

ไม่ถูกต้อง: ข้อจำกัดของคำค้นหาไม่ตรงกับข้อจำกัดของกฎความปลอดภัย

// This query will fail
db.collection("stories").get()

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

ในทางตรงกันข้าม การค้นหาต่อไปนี้จะสำเร็จเนื่องจากมีข้อจำกัดเดียวกันในช่อง author เป็นกฎความปลอดภัย

ถูกต้อง: ข้อจำกัดของคำค้นหาตรงกับข้อจำกัดของกฎความปลอดภัย

var user = firebase.auth().currentUser;

db.collection("stories").where("author", "==", user.uid).get()

รักษาความปลอดภัยและค้นหาเอกสารตามช่อง

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

service cloud.firestore {
  match /databases/{database}/documents {
    match /stories/{storyid} {
      // Anyone can read a published story; only story authors can read unpublished stories
      allow read: if resource.data.published == true || (request.auth != null && request.auth.uid == resource.data.author);
      // Only story authors can write
      allow write: if request.auth != null && request.auth.uid == resource.data.author;
    }
  }
}

การค้นหาหน้าเว็บที่เผยแพร่ต้องมีข้อจำกัดเดียวกันกับกฎความปลอดภัย ดังนี้

db.collection("stories").where("published", "==", true).get()

ข้อจำกัดการค้นหา .where("published", "==", true) รับประกันว่า resource.data.published คือ true สำหรับผลลัพธ์ใดๆ ดังนั้น การค้นหานี้จึงเป็นไปตามกฎความปลอดภัยและได้รับอนุญาตให้อ่านข้อมูล

คำค้นหา OR รายการ

เมื่อประเมินการค้นหา OR เชิงตรรกะ (or, in หรือ array-contains-any) กับชุดกฎ กฎจะประเมินค่าการเปรียบเทียบแต่ละค่าแยกกัน ค่าการเปรียบเทียบแต่ละค่าต้องเป็นไปตามข้อจำกัดของกฎความปลอดภัย ตัวอย่างเช่น สำหรับกฎต่อไปนี้

match /mydocuments/{doc} {
  allow read: if resource.data.x > 5;
}

ไม่ถูกต้อง: คำค้นหาไม่รับประกันว่า x > 5 สำหรับเอกสารที่เป็นไปได้ทั้งหมด

// These queries will fail
query(db.collection("mydocuments"),
      or(where("x", "==", 1),
         where("x", "==", 6)
      )
    )

query(db.collection("mydocuments"),
      where("x", "in", [1, 3, 6, 42, 99])
    )

ถูกต้อง: การค้นหารับประกันว่า x > 5 สำหรับเอกสารที่เป็นไปได้ทั้งหมด

query(db.collection("mydocuments"),
      or(where("x", "==", 6),
         where("x", "==", 42)
      )
    )

query(db.collection("mydocuments"),
      where("x", "in", [6, 42, 99, 105, 200])
    )

การประเมินข้อจำกัดในการค้นหา

นอกจากนี้ กฎความปลอดภัยยังยอมรับหรือปฏิเสธการค้นหาตามข้อจำกัดได้ด้วย ตัวแปร request.query มีพร็อพเพอร์ตี้ limit, offset และ orderBy ของการค้นหา ตัวอย่างเช่น กฎความปลอดภัยสามารถปฏิเสธการค้นหาที่ไม่จำกัดจำนวนเอกสารสูงสุดที่ดึงข้อมูลมายังช่วงที่กำหนดได้ ดังนี้

allow list: if request.query.limit <= 10;

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

  • ชุดกฎจะแยกกฎการอ่านออกเป็นกฎสำหรับ get และ list
  • กฎ get จะจำกัดการดึงข้อมูลเอกสารรายการเดียวให้เป็นเอกสารสาธารณะหรือเอกสารที่ผู้ใช้เป็นผู้เขียน
  • กฎ list ใช้ข้อจำกัดเดียวกันกับ get แต่ใช้สำหรับการค้นหา นอกจากนี้ยังตรวจสอบขีดจำกัดการค้นหา แล้วปฏิเสธการค้นหาที่ไม่มีขีดจำกัดหรือมีขีดจำกัดมากกว่า 10 รายการ
  • ชุดกฎจะกำหนดฟังก์ชัน authorOrPublished() เพื่อหลีกเลี่ยงการทำซ้ำโค้ด
service cloud.firestore {

  match /databases/{database}/documents {

    match /stories/{storyid} {

      // Returns `true` if the requested story is 'published'
      // or the user authored the story
      function authorOrPublished() {
        return resource.data.published == true || request.auth.uid == resource.data.author;
      }

      // Deny any query not limited to 10 or fewer documents
      // Anyone can query published stories
      // Authors can query their unpublished stories
      allow list: if request.query.limit <= 10 &&
                     authorOrPublished();

      // Anyone can retrieve a published story
      // Only a story's author can retrieve an unpublished story
      allow get: if authorOrPublished();

      // Only a story's author can write to a story
      allow write: if request.auth.uid == resource.data.author;
    }

  }
}

การค้นหากลุ่มคอลเล็กชันและกฎความปลอดภัย

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

รักษาความปลอดภัยและค้นหาเอกสารตามกลุ่มคอลเล็กชัน

ในกฎความปลอดภัย คุณต้องอนุญาตการค้นหากลุ่มคอลเล็กชันอย่างชัดแจ้งโดยเขียนกฎสำหรับกลุ่มคอลเล็กชัน ดังนี้

  1. ตรวจสอบว่า rules_version = '2'; เป็นบรรทัดแรกของชุดกฎ การค้นหากลุ่มคอลเล็กชันต้องใช้ลักษณะการทำงานของไวลด์การ์ดแบบเกิดซ้ำ {name=**} ใหม่ของกฎความปลอดภัยเวอร์ชัน 2
  2. เขียนกฎสำหรับกลุ่มคอลเล็กชันโดยใช้ match /{path=**}/[COLLECTION_ID]/{doc}

ตัวอย่างเช่น ลองพิจารณาฟอรัมที่จัดระเบียบเป็นเอกสาร forum ที่มีคอลเล็กชันย่อย posts ดังนี้

/forums/{forumid}/posts/{postid}

{
  author: "some_auth_id",
  authorname: "some_username",
  content: "I just read a great story.",
}

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

service cloud.firestore {
  match /databases/{database}/documents {
    match /forums/{forumid}/posts/{post} {
      // Only authenticated users can read
      allow read: if request.auth != null;
      // Only the post author can write
      allow write: if request.auth != null && request.auth.uid == resource.data.author;
    }
  }
}

ผู้ใช้ที่ผ่านการตรวจสอบสิทธิ์แล้วจะดึงข้อมูลโพสต์ของฟอรัมใดก็ได้ ดังนี้

db.collection("forums/technology/posts").get()

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

var user = firebase.auth().currentUser;

db.collectionGroup("posts").where("author", "==", user.uid).get()

ในกฎความปลอดภัย คุณต้องอนุญาตการค้นหานี้โดยเขียนกฎการอ่านหรือการแสดงรายการสำหรับกลุ่มคอลเล็กชัน posts ดังนี้

rules_version = '2';
service cloud.firestore {

  match /databases/{database}/documents {
    // Authenticated users can query the posts collection group
    // Applies to collection queries, collection group queries, and
    // single document retrievals
    match /{path=**}/posts/{post} {
      allow read: if request.auth != null;
    }
    match /forums/{forumid}/posts/{postid} {
      // Only a post's author can write to a post
      allow write: if request.auth != null && request.auth.uid == resource.data.author;

    }
  }
}

อย่างไรก็ตาม โปรดทราบว่ากฎเหล่านี้จะมีผลกับคอลเล็กชันทั้งหมดที่มีรหัส posts ไม่ว่าจะอยู่ในลำดับชั้นใดก็ตาม ตัวอย่างเช่น กฎเหล่านี้มีผลกับคอลเล็กชัน posts ต่อไปนี้ทั้งหมด

  • /posts/{postid}
  • /forums/{forumid}/posts/{postid}
  • /forums/{forumid}/subforum/{subforumid}/posts/{postid}

รักษาความปลอดภัยให้การค้นหากลุ่มคอลเล็กชันตามฟิลด์

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

/forums/{forumid}/posts/{postid}

{
  author: "some_auth_id",
  authorname: "some_username",
  content: "I just read a great story.",
  published: false
}

จากนั้นเราจะเขียนกฎสำหรับกลุ่มคอลเล็กชัน posts โดยอิงตามสถานะ published และ author ของโพสต์ได้ดังนี้

rules_version = '2';
service cloud.firestore {

  match /databases/{database}/documents {

    // Returns `true` if the requested post is 'published'
    // or the user authored the post
    function authorOrPublished() {
      return resource.data.published == true || request.auth.uid == resource.data.author;
    }

    match /{path=**}/posts/{post} {

      // Anyone can query published posts
      // Authors can query their unpublished posts
      allow list: if authorOrPublished();

      // Anyone can retrieve a published post
      // Authors can retrieve an unpublished post
      allow get: if authorOrPublished();
    }

    match /forums/{forumid}/posts/{postid} {
      // Only a post's author can write to a post
      allow write: if request.auth.uid == resource.data.author;
    }
  }
}

กฎเหล่านี้ช่วยให้ไคลเอ็นต์บนเว็บ, Apple และ Android ทำการค้นหาต่อไปนี้ได้

  • ทุกคนสามารถเรียกดูโพสต์ที่เผยแพร่ในฟอรัมได้โดยทำดังนี้

    db.collection("forums/technology/posts").where('published', '==', true).get()
    
  • ทุกคนสามารถเรียกดูโพสต์ที่เผยแพร่ของผู้เขียนในฟอรัมทั้งหมดได้โดยทำดังนี้

    db.collectionGroup("posts").where("author", "==", "some_auth_id").where('published', '==', true).get()
    
  • ผู้เขียนสามารถเรียกดูโพสต์ทั้งหมดที่เผยแพร่และไม่ได้เผยแพร่ในฟอรัมทั้งหมดได้โดยทำดังนี้

    var user = firebase.auth().currentUser;
    
    db.collectionGroup("posts").where("author", "==", user.uid).get()
    

รักษาความปลอดภัยและค้นหาเอกสารตามกลุ่มคอลเล็กชันและเส้นทางเอกสาร

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

ลองพิจารณาแอปพลิเคชันที่ติดตามธุรกรรมของผู้ใช้แต่ละรายในตลาดแลกเปลี่ยนหุ้นและสกุลเงินดิจิทัลหลายแห่ง

/users/{userid}/exchange/{exchangeid}/transactions/{transaction}

{
  amount: 100,
  exchange: 'some_exchange_name',
  timestamp: April 1, 2019 at 12:00:00 PM UTC-7,
  user: "some_auth_id",
}

สังเกตช่อง user แม้ว่าเราจะทราบว่าผู้ใช้รายใดเป็นเจ้าของเอกสาร transaction จากเส้นทางของเอกสาร แต่เราจะทำซ้ำข้อมูลนี้ในเอกสาร transaction แต่ละรายการเนื่องจากจะช่วยให้เราทำสิ่งต่อไปนี้ได้

  • เขียนคำค้นหากลุ่มคอลเล็กชันที่จำกัดเฉพาะเอกสารที่มี /users/{userid} ที่เฉพาะเจาะจงในเส้นทางเอกสาร เช่น

    var user = firebase.auth().currentUser;
    // Return current user's last five transactions across all exchanges
    db.collectionGroup("transactions").where("user", "==", user).orderBy('timestamp').limit(5)
    
  • บังคับใช้ข้อจำกัดนี้กับการค้นหาทั้งหมดในกลุ่มคอลเล็กชัน transactions เพื่อไม่ให้ผู้ใช้รายหนึ่งดึงข้อมูลเอกสาร transaction ของผู้ใช้รายอื่นได้

เราบังคับใช้ข้อจำกัดนี้ในกฎความปลอดภัยและรวมการตรวจสอบข้อมูลสำหรับช่อง user ดังนี้

rules_version = '2';
service cloud.firestore {

  match /databases/{database}/documents {

    match /{path=**}/transactions/{transaction} {
      // Authenticated users can retrieve only their own transactions
      allow read: if resource.data.user == request.auth.uid;
    }

    match /users/{userid}/exchange/{exchangeid}/transactions/{transaction} {
      // Authenticated users can write to their own transactions subcollections
      // Writes must populate the user field with the correct auth id
      allow write: if userid == request.auth.uid && request.data.user == request.auth.uid
    }
  }
}

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