Catch up on highlights from Firebase at Google I/O 2023. Learn more

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

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

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

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

แบบสอบถามและกฎความปลอดภัย

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

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

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

/story/{storyid}

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

นอกจาก title และ content แล้ว เอกสารแต่ละฉบับยังจัดเก็บ author และฟิลด์ published เพื่อใช้สำหรับการควบคุมการเข้าถึง ตัวอย่างเหล่านี้ถือว่าแอปใช้ Firebase Authentication เพื่อตั้งค่าฟิลด์ author เป็น UID ของผู้ใช้ที่สร้างเอกสาร การตรวจสอบความถูกต้องของ Firebase ยังเติมตัวแปร 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 ทุกฉบับก็ตาม สาเหตุของพฤติกรรมนี้คือเมื่อ Cloud Firestore ใช้กฎความปลอดภัยของคุณ ระบบจะประเมินการสืบค้นตามชุดผลลัพธ์ ที่เป็นไป ได้ ไม่ใช่เทียบกับคุณสมบัติ จริง ของเอกสารในฐานข้อมูลของคุณ หากข้อความค้นหา อาจ มีเอกสารที่ละเมิดกฎความปลอดภัยของคุณ ข้อความค้นหานั้นจะล้มเหลว

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

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

  }
}

แบบสอบถามกลุ่มคอลเลกชันและกฎความปลอดภัย

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

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

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

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

ตัวอย่างเช่น พิจารณาฟอรัมที่จัดระเบียบเป็นเอกสาร forum ที่มีคอลเล็กชันย่อยของ 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 ID โดยไม่คำนึงถึงลำดับชั้น ตัวอย่างเช่น กฎเหล่านี้ใช้กับคอลเลกชัน posts ทั้งหมดต่อไปนี้:

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

แบบสอบถามกลุ่มคอลเลกชันที่ปลอดภัยตามฟิลด์

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

/ฟอรัม/{ฟอรั่ม}/โพสต์/{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
    }
  }
}

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