ควบคุมการเข้าถึงช่องที่ต้องการ

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

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

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

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

การอนุญาตสิทธิ์การเข้าถึงระดับอ่านสำหรับบางช่องเท่านั้น

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

หากมีบางช่องในเอกสารที่คุณต้องการซ่อนจากผู้ใช้บางราย วิธีที่ดีที่สุดคือใส่ช่องเหล่านั้นไว้ในเอกสารแยกต่างหาก ตัวอย่างเช่น คุณอาจพิจารณาสร้างเอกสารในคอลเล็กชันย่อย private ดังนี้

/employees/{emp_id}

  name: "Alice Hamilton",
  department: 461,
  start_date: <timestamp>

/employees/{emp_id}/private/finances

    salary: 80000,
    bonus_mult: 1.25,
    perf_review: 4.2

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

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow any logged in user to view the public employee data
    match /employees/{emp_id} {
      allow read: if request.resource.auth != null
      // Allow only users with the custom auth claim of "Finance" to view
      // the employee's financial data
      match /private/finances {
        allow read: if request.resource.auth &&
          request.resource.auth.token.role == 'Finance'
      }
    }
  }
}

การจำกัดช่องในการสร้างเอกสาร

กฎไม่มีสคีมา ซึ่งหมายความว่าไม่มีข้อจำกัดในระดับฐานข้อมูลสำหรับฟิลด์ที่เอกสารมี แม้ว่าความยืดหยุ่นนี้จะช่วยให้การพัฒนาทำได้ง่ายขึ้น แต่ก็จะมีบางครั้งที่คุณต้องการตรวจสอบว่าไคลเอ็นต์สามารถสร้างเอกสารที่มีเฉพาะฟิลด์ที่เฉพาะเจาะจง หรือไม่มีฟิลด์อื่นๆ

คุณสร้างกฎเหล่านี้ได้โดยตรวจสอบเมธอด keys ของออบเจ็กต์ request.resource.data นี่คือรายการของช่องทั้งหมดที่ไคลเอ็นต์ พยายามเขียนในเอกสารใหม่นี้ เมื่อรวมชุดช่องนี้เข้ากับฟังก์ชันต่างๆ เช่น hasOnly() หรือ hasAny() คุณจะเพิ่มตรรกะที่จำกัดประเภทเอกสารที่ผู้ใช้สามารถเพิ่มลงในกฎได้

การกำหนดให้ต้องกรอกข้อมูลในช่องที่เฉพาะเจาะจงในเอกสารใหม่

สมมติว่าคุณต้องการตรวจสอบว่าเอกสารทั้งหมดที่สร้างในคอลเล็กชัน restaurant มีช่อง name, location และ city เป็นอย่างน้อย คุณทำได้โดยเรียกใช้ hasAll() กับรายการคีย์ในเอกสารใหม่

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to create a document only if that document contains a name
    // location, and city field
    match /restaurant/{restId} {
      allow create: if request.resource.data.keys().hasAll(['name', 'location', 'city']);
    }
  }
}

ซึ่งช่วยให้สร้างร้านอาหารที่มีฟิลด์อื่นๆ ได้ด้วย แต่จะช่วยให้มั่นใจว่าเอกสารทั้งหมดที่สร้างโดยไคลเอ็นต์มีฟิลด์อย่างน้อย 3 ฟิลด์นี้

การห้ามใช้บางฟิลด์ในเอกสารใหม่

ในทำนองเดียวกัน คุณสามารถป้องกันไม่ให้ไคลเอ็นต์สร้างเอกสารที่มีช่องที่เฉพาะเจาะจงได้โดยใช้ hasAny() กับรายการช่องที่ไม่อนุญาต เมธอดนี้จะประเมินค่าเป็น "จริง" หากเอกสารมีช่องใดช่องหนึ่งเหล่านี้ ดังนั้นคุณอาจต้องการปฏิเสธผลลัพธ์เพื่อห้ามไม่ให้มีช่องบางช่อง

ตัวอย่างเช่น ในตัวอย่างต่อไปนี้ ไคลเอ็นต์ไม่ได้รับอนุญาตให้สร้างเอกสารที่มีช่อง average_score หรือ rating_count เนื่องจากระบบจะเพิ่มช่องเหล่านี้โดยการเรียกใช้เซิร์ฟเวอร์ในภายหลัง

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to create a document only if that document does *not*
    // contain an average_score or rating_count field.
    match /restaurant/{restId} {
      allow create: if (!request.resource.data.keys().hasAny(
        ['average_score', 'rating_count']));
    }
  }
}

การสร้างรายการช่องที่อนุญาตสำหรับเอกสารใหม่

แทนที่จะห้ามใช้บางฟิลด์ในเอกสารใหม่ คุณอาจต้องการสร้างรายการเฉพาะฟิลด์ที่อนุญาตอย่างชัดแจ้งในเอกสารใหม่ จากนั้นคุณสามารถใช้ฟังก์ชัน hasOnly() เพื่อตรวจสอบว่าเอกสารใหม่ที่สร้างขึ้นมีเฉพาะช่องเหล่านี้ (หรือชุดย่อยของช่องเหล่านี้) และไม่มีช่องอื่นๆ

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to create a document only if that document doesn't contain
    // any fields besides the ones listed below.
    match /restaurant/{restId} {
      allow create: if (request.resource.data.keys().hasOnly(
        ['name', 'location', 'city', 'address', 'hours', 'cuisine']));
    }
  }
}

การรวมช่องที่ต้องกรอกและช่องที่ไม่บังคับ

คุณสามารถรวมการดำเนินการ hasAll และ hasOnly เข้าด้วยกันในกฎความปลอดภัยเพื่อกำหนดให้ต้องมีบางฟิลด์และอนุญาตฟิลด์อื่นๆ ตัวอย่างเช่น ตัวอย่างนี้กำหนดให้เอกสารใหม่ทั้งหมดต้องมีฟิลด์ name, location และ city และอนุญาตให้มีฟิลด์ address, hours และ cuisine ได้ (ไม่บังคับ)

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to create a document only if that document has a name,
    // location, and city field, and optionally address, hours, or cuisine field
    match /restaurant/{restId} {
      allow create: if (request.resource.data.keys().hasAll(['name', 'location', 'city'])) &&
       (request.resource.data.keys().hasOnly(
           ['name', 'location', 'city', 'address', 'hours', 'cuisine']));
    }
  }
}

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

service cloud.firestore {
  match /databases/{database}/documents {
    function verifyFields(required, optional) {
      let allAllowedFields = required.concat(optional);
      return request.resource.data.keys().hasAll(required) &&
        request.resource.data.keys().hasOnly(allAllowedFields);
    }
    match /restaurant/{restId} {
      allow create: if verifyFields(['name', 'location', 'city'],
        ['address', 'hours', 'cuisine']);
    }
  }
}

การจำกัดช่องในการอัปเดต

แนวทางปฏิบัติด้านความปลอดภัยที่พบบ่อยคือการอนุญาตให้ไคลเอ็นต์แก้ไขได้เฉพาะบางฟิลด์เท่านั้น คุณไม่สามารถทำเช่นนี้ได้โดยดูที่รายการ request.resource.data.keys() ที่อธิบายไว้ในส่วนก่อนหน้าเพียงอย่างเดียว เนื่องจากรายการนี้แสดงถึงเอกสารที่สมบูรณ์ตามที่จะปรากฏหลังจากการอัปเดต ดังนั้นจึงรวมฟิลด์ที่ไคลเอ็นต์ไม่ได้เปลี่ยนแปลง

อย่างไรก็ตาม หากคุณใช้ฟังก์ชัน diff() คุณจะเปรียบเทียบ request.resource.data กับออบเจ็กต์ resource.data ซึ่งแสดงถึงเอกสารในฐานข้อมูลก่อนการอัปเดตได้ ซึ่งจะสร้างออบเจ็กต์ mapDiff ซึ่งเป็นออบเจ็กต์ที่มีการเปลี่ยนแปลงทั้งหมดระหว่างแผนที่ 2 รายการที่แตกต่างกัน

การเรียกใช้เมธอด affectedKeys() ใน mapDiff นี้จะช่วยให้คุณได้ชุดฟิลด์ที่มีการเปลี่ยนแปลงในการแก้ไข จากนั้นคุณสามารถใช้ฟังก์ชันอย่าง hasOnly() หรือ hasAny() เพื่อให้แน่ใจว่าชุดนี้มี (หรือไม่มี) รายการบางรายการ

การป้องกันไม่ให้มีการเปลี่ยนแปลงบางช่อง

เมื่อใช้เมธอด hasAny() กับชุดที่สร้างโดย affectedKeys() แล้วปฏิเสธผลลัพธ์ คุณจะปฏิเสธคำขอของไคลเอ็นต์ที่พยายามเปลี่ยนแปลงฟิลด์ที่คุณไม่ต้องการให้เปลี่ยนแปลงได้

เช่น คุณอาจต้องการอนุญาตให้ลูกค้าอัปเดตข้อมูลเกี่ยวกับร้านอาหาร แต่ไม่ให้เปลี่ยนคะแนนเฉลี่ยหรือจำนวนรีวิว

service cloud.firestore {
  match /databases/{database}/documents {
    match /restaurant/{restId} {
      // Allow the client to update a document only if that document doesn't
      // change the average_score or rating_count fields
      allow update: if (!request.resource.data.diff(resource.data).affectedKeys()
        .hasAny(['average_score', 'rating_count']));
    }
  }
}

อนุญาตให้เปลี่ยนแปลงได้เฉพาะบางฟิลด์

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

ตัวอย่างเช่น แทนที่จะไม่อนุญาตช่อง average_score และ rating_count คุณสามารถสร้างกฎความปลอดภัยที่อนุญาตให้ไคลเอ็นต์เปลี่ยนเฉพาะช่อง name, location, city, address, hours และ cuisine ได้

service cloud.firestore {
  match /databases/{database}/documents {
    match /restaurant/{restId} {
    // Allow a client to update only these 6 fields in a document
      allow update: if (request.resource.data.diff(resource.data).affectedKeys()
        .hasOnly(['name', 'location', 'city', 'address', 'hours', 'cuisine']));
    }
  }
}

ซึ่งหมายความว่าหากในอนาคตแอปของคุณมีการปรับปรุงเอกสารร้านอาหารให้มีช่อง telephone การพยายามแก้ไขช่องดังกล่าวจะล้มเหลวจนกว่าคุณจะกลับไปเพิ่มช่องนั้นลงในรายการ hasOnly() ในกฎการรักษาความปลอดภัย

การบังคับใช้ประเภทฟิลด์

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

ตัวอย่างเช่น กฎความปลอดภัยต่อไปนี้บังคับใช้ว่าช่อง score ของรีวิวต้องเป็นจำนวนเต็ม ช่อง headline, content และ author_name เป็นสตริง และ review_date เป็นการประทับเวลา

service cloud.firestore {
  match /databases/{database}/documents {
    match /restaurant/{restId} {
      // Restaurant rules go here...
      match /review/{reviewId} {
        allow create: if (request.resource.data.score is int &&
          request.resource.data.headline is string &&
          request.resource.data.content is string &&
          request.resource.data.author_name is string &&
          request.resource.data.review_date is timestamp
        );
      }
    }
  }
}

ประเภทข้อมูลที่ถูกต้องสำหรับโอเปอเรเตอร์ is ได้แก่ bool, bytes, float, int, list, latlng, number, path, map, string และ timestamp โอเปอเรเตอร์ is ยังรองรับประเภทข้อมูล constraint, duration, set และ map_diff ด้วย แต่เนื่องจากประเภทข้อมูลเหล่านี้สร้างขึ้นโดยภาษาของกฎความปลอดภัยเอง ไม่ได้สร้างขึ้นโดยไคลเอ็นต์ คุณจึงไม่ค่อยได้ใช้ประเภทข้อมูลเหล่านี้ในแอปพลิเคชันที่ใช้งานจริงส่วนใหญ่

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

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

ตัวอย่างเช่น กฎต่อไปนี้ช่วยให้มั่นใจว่าช่อง tags ในเอกสารมีรายการและรายการแรกเป็นสตริง นอกจากนี้ยังช่วยให้มั่นใจว่าช่อง product มีแผนที่ซึ่งมีชื่อสินค้าที่เป็นสตริงและจำนวนที่เป็นจำนวนเต็ม

service cloud.firestore {
  match /databases/{database}/documents {
  match /orders/{orderId} {
    allow create: if request.resource.data.tags is list &&
      request.resource.data.tags[0] is string &&
      request.resource.data.product is map &&
      request.resource.data.product.name is string &&
      request.resource.data.product.quantity is int
      }
    }
  }
}

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

service cloud.firestore {
  match /databases/{database}/documents {

  function reviewFieldsAreValidTypes(docData) {
     return docData.score is int &&
          docData.headline is string &&
          docData.content is string &&
          docData.author_name is string &&
          docData.review_date is timestamp;
  }

   match /restaurant/{restId} {
      // Restaurant rules go here...
      match /review/{reviewId} {
        allow create: if reviewFieldsAreValidTypes(request.resource.data) &&
          // Other rules may go here
        allow update: if reviewFieldsAreValidTypes(request.resource.data) &&
          // Other rules may go here
      }
    }
  }
}

การบังคับใช้ประเภทสำหรับฟิลด์ตัวเลือก

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

ตัวอย่างเช่น หากเอกสารรีวิวยังมีฟิลด์ตัวเลือก photo_url และฟิลด์ตัวเลือก tags ซึ่งคุณต้องการยืนยันว่าเป็นสตริงและรายการตามลำดับ คุณสามารถทำได้โดยเขียนฟังก์ชัน reviewFieldsAreValidTypes ใหม่ให้มีลักษณะดังต่อไปนี้

  function reviewFieldsAreValidTypes(docData) {
     return docData.score is int &&
          docData.headline is string &&
          docData.content is string &&
          docData.author_name is string &&
          docData.review_date is timestamp &&
          docData.get('photo_url', '') is string &&
          docData.get('tags', []) is list;
  }

ซึ่งจะปฏิเสธเอกสารที่มี tags แต่ไม่ใช่รายการ ขณะที่ยังอนุญาตเอกสารที่ไม่มีช่อง tags (หรือ photo_url)

ไม่อนุญาตให้เขียนบางส่วนโดยเด็ดขาด

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