ใช้เงื่อนไขในกฎการรักษาความปลอดภัยของ Firebase Cloud Storage

คู่มือนี้ต่อยอดจากคู่มือการเรียนรู้ไวยากรณ์หลักของภาษาFirebase Security Rules guide เพื่อแสดงวิธีเพิ่มเงื่อนไขลงในFirebase Security RulesสำหรับCloud Storage

องค์ประกอบพื้นฐานหลักของ Cloud Storage Security Rules คือ เงื่อนไข เงื่อนไขคือนิพจน์บูลีนที่กำหนดว่าควรอนุญาตหรือปฏิเสธการดำเนินการหนึ่งๆ สำหรับกฎพื้นฐาน การใช้ตัวอักษร true และ false เป็นเงื่อนไขนั้นใช้ได้ดี แต่Firebase Security RulesสำหรับCloud Storage ภาษา มีวิธีเขียนเงื่อนไขที่ซับซ้อนมากขึ้นซึ่งทำสิ่งต่อไปนี้ได้

  • ตรวจสอบการตรวจสอบสิทธิ์ของผู้ใช้
  • ตรวจสอบข้อมูลขาเข้า

การตรวจสอบสิทธิ์

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

เมื่อผู้ใช้ที่ผ่านการตรวจสอบสิทธิ์ทำการขอไปยัง Cloud Storage ระบบจะป้อนข้อมูลลงในตัวแปร request.auth ด้วย uid ของผู้ใช้ (request.auth.uid) รวมถึงการอ้างสิทธิ์ของ Firebase Authentication JWT (request.auth.token)

นอกจากนี้ เมื่อใช้การตรวจสอบสิทธิ์ที่กำหนดเอง ระบบจะแสดงการอ้างสิทธิ์เพิ่มเติมในช่อง request.auth.token

เมื่อผู้ใช้ที่ยังไม่ผ่านการตรวจสอบสิทธิ์ทำการขอ ตัวแปร request.auth จะเป็น null

การใช้ข้อมูลนี้มีวิธีทั่วไปหลายวิธีในการใช้การตรวจสอบสิทธิ์เพื่อรักษาความปลอดภัยของไฟล์ ดังนี้

  • สาธารณะ: ละเว้น request.auth
  • ส่วนตัวที่ผ่านการตรวจสอบสิทธิ์: ตรวจสอบว่า request.auth ไม่ใช่ null
  • ส่วนตัวของผู้ใช้: ตรวจสอบว่า request.auth.uid เท่ากับ uid ของเส้นทาง
  • ส่วนตัวของกลุ่ม: ตรวจสอบการอ้างสิทธิ์ของโทเค็นที่กำหนดเองให้ตรงกับการอ้างสิทธิ์ที่เลือก หรืออ่านข้อมูลเมตาของไฟล์เพื่อดูว่ามีช่องข้อมูลเมตาอยู่หรือไม่

สาธารณะ

กฎใดก็ตามที่ไม่ได้พิจารณาบริบท request.auth ถือเป็นกฎ public เนื่องจากไม่ได้พิจารณาบริบทการตรวจสอบสิทธิ์ของผู้ใช้ กฎเหล่านี้มีประโยชน์สำหรับการแสดงข้อมูลสาธารณะ เช่น ชิ้นงานเกม ไฟล์เสียง หรือเนื้อหาคงที่อื่นๆ

// Anyone to read a public image if the file is less than 100kB
// Anyone can upload a public file ending in '.txt'
match /public/{imageId} {
  allow read: if resource.size < 100 * 1024;
  allow write: if imageId.matches(".*\\.txt");
}

ส่วนตัวที่ผ่านการตรวจสอบสิทธิ์

ในบางกรณี คุณอาจต้องการให้ผู้ใช้ที่ผ่านการตรวจสอบสิทธิ์ทั้งหมดของแอปพลิเคชันดูข้อมูลได้ แต่ผู้ใช้ที่ยังไม่ผ่านการตรวจสอบสิทธิ์จะดูไม่ได้ เนื่องจากตัวแปร request.auth เป็น null สำหรับผู้ใช้ที่ยังไม่ผ่านการตรวจสอบสิทธิ์ทั้งหมด สิ่งที่คุณต้องทำคือตรวจสอบว่าตัวแปร request.auth มีอยู่หรือไม่เพื่อกำหนดให้ต้องมีการตรวจสอบสิทธิ์

// Require authentication on all internal image reads
match /internal/{imageId} {
  allow read: if request.auth != null;
}

ส่วนตัวของผู้ใช้

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

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

// Only a user can upload their profile picture, but anyone can view it
match /users/{userId}/profilePicture.png {
  allow read;
  allow write: if request.auth.uid == userId;
}

ส่วนตัวของกลุ่ม

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

  • สร้างโทเค็นที่กำหนดเองของ Firebase Authentication Firebase Authentication ซึ่งมีข้อมูลเพิ่มเติมเกี่ยวกับสมาชิกในกลุ่ม (เช่น รหัสกลุ่ม)
  • ใส่ข้อมูลกลุ่ม (เช่น รหัสกลุ่มหรือรายการ uid ที่ได้รับอนุญาต) ใน ข้อมูลเมตาของ ไฟล์

เมื่อจัดเก็บข้อมูลนี้ไว้ในโทเค็นหรือข้อมูลเมตาของไฟล์แล้ว คุณจะอ้างอิงข้อมูลนี้จากภายในกฎได้

// Allow reads if the group ID in your token matches the file metadata's `owner` property
// Allow writes if the group ID is in the user's custom token
match /files/{groupId}/{fileName} {
  allow read: if resource.metadata.owner == request.auth.token.groupId;
  allow write: if request.auth.token.groupId == groupId;
}

การประเมินคำขอ

ระบบจะประเมินการอัปโหลด การดาวน์โหลด การเปลี่ยนแปลงข้อมูลเมตา และการลบโดยใช้ request ที่ส่งไปยัง Cloud Storage นอกจากรหัสที่ไม่ซ้ำกันของผู้ใช้และ เพย์โหลด Firebase Authentication ในออบเจ็กต์ request.auth ตามที่อธิบายไว้ข้างต้นแล้ว ตัวแปร request ยังมีเส้นทางไฟล์ที่กำลังดำเนินการคำขอ เวลาที่ได้รับคำขอ และค่า resource ใหม่ หากคำขอเป็นการเขียน

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

รายการพร็อพเพอร์ตี้ทั้งหมดในออบเจ็กต์ request มีดังนี้

พร็อพเพอร์ตี้ ประเภท คำอธิบาย
auth map<string, string> เมื่อผู้ใช้เข้าสู่ระบบ จะระบุ uid ซึ่งเป็นรหัสที่ไม่ซ้ำกันของผู้ใช้ และ token ซึ่งเป็นแผนที่ของการอ้างสิทธิ์ JWT Firebase Authentication หากไม่เป็นเช่นนั้น ค่าจะเป็น null
params map<string, string> แผนที่ที่มีพารามิเตอร์การค้นหาของคำขอ
path path A path ที่แสดงเส้นทางที่กำลังดำเนินการคำขอ
resource map<string, string> ค่าทรัพยากรใหม่ ซึ่งมีเฉพาะในคำขอ write
time timestamp การประทับเวลาที่แสดงเวลาเซิร์ฟเวอร์ที่ประเมินคำขอ

การประเมินทรัพยากร

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

Firebase Security Rules สำหรับ Cloud Storage มีข้อมูลเมตาของไฟล์ในออบเจ็กต์ resource ซึ่งมีคู่คีย์/ค่าของข้อมูลเมตาที่แสดงในออบเจ็กต์ Cloud Storage คุณสามารถตรวจสอบพร็อพเพอร์ตี้เหล่านี้ในคำขอ read หรือ write เพื่อให้มั่นใจในความสมบูรณ์ของข้อมูล

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

รายการพร็อพเพอร์ตี้ทั้งหมดในออบเจ็กต์ resource มีดังนี้

พร็อพเพอร์ตี้ ประเภท คำอธิบาย
name string ชื่อเต็มของออบเจ็กต์
bucket string ชื่อบัคเก็ตที่ออบเจ็กต์นี้อยู่
generation int รุ่นออบเจ็กต์ Google Cloud Storage ของออบเจ็กต์นี้
metageneration int รุ่นข้อมูลเมตาของออบเจ็กต์ Google Cloud Storage ของออบเจ็กต์นี้
size int ขนาดของออบเจ็กต์ในหน่วยไบต์
timeCreated timestamp การประทับเวลาที่แสดงเวลาที่สร้างออบเจ็กต์
updated timestamp การประทับเวลาที่แสดงเวลาที่อัปเดตออบเจ็กต์ครั้งล่าสุด
md5Hash string แฮช MD5 ของออบเจ็กต์
crc32c string แฮช crc32c ของออบเจ็กต์
etag string etag ที่เชื่อมโยงกับออบเจ็กต์นี้
contentDisposition string การจัดวางเนื้อหาที่เชื่อมโยงกับออบเจ็กต์นี้
contentEncoding string การเข้ารหัสเนื้อหาที่เชื่อมโยงกับออบเจ็กต์นี้
contentLanguage string ภาษาของเนื้อหาที่เชื่อมโยงกับออบเจ็กต์นี้
contentType string ประเภทเนื้อหาที่เชื่อมโยงกับออบเจ็กต์นี้
metadata map<string, string> คู่คีย์/ค่าของข้อมูลเมตาที่กำหนดเองเพิ่มเติมที่นักพัฒนาซอฟต์แวร์ระบุ

request.resource มีข้อมูลทั้งหมดนี้ ยกเว้น generation, metageneration, etag, timeCreated และ updated

ปรับปรุงด้วย Cloud Firestore

คุณสามารถเข้าถึงเอกสารใน Cloud Firestore เพื่อประเมินเกณฑ์การให้สิทธิ์อื่นๆ ได้

การใช้ฟังก์ชัน firestore.get() และ firestore.exists() กฎการรักษาความปลอดภัย จะประเมินคำขอขาเข้าเทียบกับเอกสารใน Cloud Firestore ได้ ฟังก์ชัน firestore.get() และ firestore.exists() ทั้ง 2 ฟังก์ชันคาดหวังเส้นทางเอกสารที่ระบุไว้อย่างครบถ้วน เมื่อใช้ตัวแปรเพื่อสร้างเส้นทางสำหรับ firestore.get() และ firestore.exists() คุณต้องหลีกตัวแปรอย่างชัดแจ้งโดยใช้ไวยากรณ์ $(variable)

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

service firebase.storage {
  match /b/{bucket}/o {
    match /users/{club}/files/{fileId} {
      allow read: if club in
        firestore.get(/databases/(default)/documents/users/$(request.auth.id)).data.memberships
    }
  }
}
ในตัวอย่างถัดไป มีเพียงเพื่อนของผู้ใช้เท่านั้นที่จะเห็นรูปภาพของผู้ใช้
service firebase.storage {
  match /b/{bucket}/o {
    match /users/{userId}/photos/{fileId} {
      allow read: if
        firestore.exists(/databases/(default)/documents/users/$(userId)/friends/$(request.auth.id))
    }
  }
}

เมื่อสร้างและบันทึก Cloud Storage Security Rules แรกที่ใช้ Cloud Firestore ฟังก์ชันเหล่านี้ ระบบจะแจ้งให้คุณเปิดใช้สิทธิ์ในการเชื่อมต่อผลิตภัณฑ์ทั้ง 2 รายการในคอนโซล Firebase หรือ Firebase CLI

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

ตรวจสอบข้อมูล

Firebase Security Rules สำหรับ Cloud Storage ยังใช้เพื่อตรวจสอบข้อมูลได้ด้วย ซึ่งรวมถึง การตรวจสอบชื่อและเส้นทางของไฟล์ รวมถึงพร็อพเพอร์ตี้ข้อมูลเมตาของไฟล์ เช่น contentType และ size

service firebase.storage {
  match /b/{bucket}/o {
    match /images/{imageId} {
      // Only allow uploads of any image file that's less than 5MB
      allow write: if request.resource.size < 5 * 1024 * 1024
                   && request.resource.contentType.matches('image/.*');
    }
  }
}

ฟังก์ชันที่กำหนดเอง

เมื่อ Firebase Security Rules ซับซ้อนมากขึ้น คุณอาจต้องการรวมชุด เงื่อนไขไว้ในฟังก์ชันที่คุณนำไปใช้ซ้ำในชุดกฎได้ กฎการรักษาความปลอดภัยรองรับฟังก์ชันที่กำหนดเอง ไวยากรณ์ของฟังก์ชันที่กำหนดเองจะคล้ายกับ JavaScript แต่ Firebase Security Rules ฟังก์ชันเขียนด้วยภาษาเฉพาะโดเมน ซึ่งมีข้อจำกัดที่สำคัญบางประการ ดังนี้

  • ฟังก์ชันจะมีคำสั่ง return เพียงคำสั่งเดียวเท่านั้น และไม่มีตรรกะเพิ่มเติม เช่น ฟังก์ชันจะวนซ้ำหรือเรียกใช้บริการภายนอกไม่ได้
  • ฟังก์ชันจะเข้าถึงฟังก์ชันและตัวแปรจากขอบเขตที่กำหนดไว้ได้โดยอัตโนมัติ เช่น ฟังก์ชันที่กำหนดไว้ใน ขอบเขต service firebase.storage จะเข้าถึงตัวแปร resource ได้ และสำหรับ Cloud Firestore เท่านั้น ฟังก์ชันในตัว เช่น get() และ exists()
  • ฟังก์ชันอาจเรียกใช้ฟังก์ชันอื่นๆ ได้ แต่จะเรียกใช้ตัวเองซ้ำไม่ได้ ความลึกของสแต็กการเรียกใช้ทั้งหมดจำกัดไว้ที่ 10
  • ในเวอร์ชัน rules2 ฟังก์ชันจะกำหนดตัวแปรได้โดยใช้คีย์เวิร์ด let ฟังก์ชันจะมี Binding `let` ได้ไม่จำกัดจำนวน แต่ต้องลงท้ายด้วยคำสั่ง `return`

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

service firebase.storage {
  match /b/{bucket}/o {
    // True if the user is signed in or the requested data is 'public'
    function signedInOrPublic() {
      return request.auth.uid != null || resource.data.visibility == 'public';
    }
    match /images/{imageId} {
      allow read, write: if signedInOrPublic();
    }
    match /mp3s/{mp3Ids} {
      allow read: if signedInOrPublic();
    }
  }
}

การใช้ฟังก์ชันใน Firebase Security Rules จะช่วยให้ดูแลรักษากฎได้ง่ายขึ้นเมื่อกฎมีความซับซ้อน มากขึ้น

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

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

ดูวิธีจัดการกรณีการใช้งานหลัก และดูเวิร์กโฟลว์สำหรับการพัฒนา การทดสอบ และการติดตั้งใช้งานกฎ