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

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

หากต้องการความช่วยเหลือจาก AI ให้คัดลอกพรอมต์ด้านล่างลงใน เอเจนต์การเขียนโค้ด AI (เช่น Gemini ใน Android Studio, Antigravity, Claude Code, Codex และ Cursor) คุณปรับแต่งพรอมต์และทำซ้ำกับเอเจนต์ได้ ดูข้อมูลการติดตั้งโดยละเอียดได้ที่ ทักษะเอเจนต์ 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 group เพื่อให้ผู้ใช้รายหนึ่งไม่สามารถดึงข้อมูลเอกสาร 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
    }
  }
}

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