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

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

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

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

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

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

รักษาความปลอดภัยและค้นหาเอกสารตาม 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 ยังเติมข้อมูลตัวแปร 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;
    }

  }
}

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

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

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

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

  1. ตรวจสอบว่า rules_version = '2'; เป็นบรรทัดแรกของชุดกฎ การค้นหากลุ่มคอลเล็กชันต้องใช้ลักษณะการทำงานของไวลด์การ์ดแบบเรียกซ้ำใหม่ของกฎความปลอดภัยเวอร์ชัน 2{name=**}
  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
    }
  }
}

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