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

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

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