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

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

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

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

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

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

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

หากมีฟิลด์บางอย่างในเอกสารที่คุณต้องการซ่อนจากผู้ใช้บางราย วิธีที่ดีที่สุดคือการใส่ฟิลด์เหล่านั้นไว้ในเอกสารแยกต่างหาก ตัวอย่างเช่น คุณอาจพิจารณาสร้างเอกสารใน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 อย่างน้อย 1 รายการ คุณทำได้โดยเรียกใช้ 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 ไม่รองรับ Generics หรืออาร์กิวเมนต์ประเภท กล่าวคือ คุณสามารถใช้กฎความปลอดภัยเพื่อบังคับให้ฟิลด์หนึ่งๆ มีรายการหรือแมป แต่ไม่สามารถบังคับให้ฟิลด์มีรายการ ของจำนวนเต็มหรือสตริงทั้งหมดได้

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

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