ในปัจจุบัน แอปพลิเคชันเว็บหลายอย่างจะมีหน้าที่เข้าถึงได้สาธารณะ — เช่นหน้าแรก — และหน้าที่ปลอดภัยมากกว่าที่ผู้ใช้ต้องล็อกอินเพื่อเข้าถึง กระบวนการลงทะเบียนผู้ใช้ การล็อกอิน และการติดตามสถานะเซสชันของผู้ใช้เรียกว่า "การรับรองตัวตน" (authentication)
ในเวลาเดียวกัน เมื่อจัดการกับผู้ใช้ที่ล็อกอินแล้ว จำเป็นต้องแยกการดำเนินการและทรัพยากรที่พวกเขาสามารถเข้าถึงได้ขึ้นอยู่กับบทบาทผู้ใช้ ตัวอย่างเช่น "admin" มักมีสิทธิ์เข้าถึงมากกว่าผู้ใช้ทั่วไป กระบวนการแยกสิทธิ์เข้าถึงของผู้ใช้ที่ได้รับการรับรองตัวตนนี้เรียกว่า "การอนุมัติ" (authorization)
ในโพสต์นี้ เราจะสำรวจไลบรารีการอนุมัติที่ได้รับความนิยมใน Ruby สองตัว: Pundit และ CanCanCan
ลองเริ่มกันเลย!
การตั้งค่าและข้อกำหนดเบื้องต้น
ในบทความนี้ เราจะใช้แอป Rails 7 เรียบง่ายที่มีผู้ใช้และโพสต์ ผู้ใช้จะได้รับบทบาท "editor" หรือ "writer" สถานการณ์แบบนี้เหมาะสำหรับแสดงให้เห็นถึงการทำงานของการอนุมัติ
ดูที่เก็บโค้ดสำหรับแอปตัวอย่างของเราพร้อม:
- Pundit
- CanCanCan
แม้ว่าบทความนี้จะเน้นการอนุมัติ แต่หัวข้อที่เกี่ยวข้องคือการรับรองตัวตนก็ไม่สามารถละเลยได้
เราจะไม่ลงลึกในรายละเอียดการตั้งค่าการรับรองตัวตน เพราะอยู่นอกขอบเขตของโพสต์นี้ คุณสามารถปฏิบัติตามคำแนะนำการติดตั้งในเอกสารของ Devise gem (เราจะจับคู่ Devise กับ gem การอนุมัติของเรา)
อีกอย่างหนึ่ง — ไม่ว่าคุณจะใช้ Pundit หรือ CanCanCan การกำหนดบทบาทผู้ใช้ของแอปไม่ได้เกิดขึ้นโดยอัตโนมัติเมื่อคุณติดตั้ง gem การอนุมัติใดๆ คุณจะต้องตั้งค่าเองด้วยตนเอง
ลองทำกันเถอะ
การกำหนดบทบาทผู้ใช้
สมมติว่าคุณติดตั้ง Devise gem และตั้งค่าโมเดลผู้ใช้แล้ว ขั้นตอนต่อไปคือการตัดสินใจว่าแอปจะมีบทบาทผู้ใช้อะไรบ้าง ในกรณีของเรา เราจะกำหนดบทบาทดังนี้:
- Writer - บทบาทนี้จะสามารถ สร้าง, แก้ไข, อัปเดต, และ ลบ โพสต์ของตัวเองได้ พร้อมทั้งสามารถ ดู โพสต์ของ writer คนอื่นได้
- Editor - ผู้ใช้ที่มีบทบาท editor สามารถ แก้ไข, อัปเดต, ดู, และ ลบ โพสต์ของผู้ใช้คนใดก็ได้ แต่ไม่สามารถ สร้าง โพสต์ของตัวเองได้
เพิ่มคอลัมน์เพื่อเก็บบทบาทของผู้ใช้ (โดยใช้ migration เพื่อแก้ไขตารางผู้ใช้):
จากนั้นรัน migration:
และแก้ไขโมเดลผู้ใช้เพื่อรวมบทบาทที่เรากำหนด:
ขณะที่เรากำลังทำอยู่ เรามาตั้งค่าโมเดล Post ด้วยแอตทริบิวต์ title และ body รวมถึงการอ้างอิงถึงผู้ใช้ที่เขียนโพสต์:
เราเพิ่ม foreign key user_id ลงในโมเดล Post เพื่อเชื่อมโยงทุกโพสต์ที่สร้างกับผู้ใช้เฉพาะคน ด้วยการที่เราตั้งค่าการรับรองตัวตนผู้ใช้ด้วย Devise แล้ว เราสามารถแก้ไขเมธอด create ของ Posts controller เพื่อตั้งค่า user_id ให้เป็นผู้ใช้ที่ล็อกอินอยู่ปัจจุบันโดยอัตโนมัติ:
เรายังสามารถแก้ไขโมเดล User เพื่อให้แน่ใจว่ามีการเชื่อมโยงกับโมเดล Post:
สุดท้าย เรามาเพิ่มข้อมูลผู้ใช้บางส่วนลงในฐานข้อมูล โดยแต่ละคนมีบทบาทต่างกัน:
จากนั้น seed ฐานข้อมูล:
จนตอนนี้ แอปของเรามี:
- การรับรองตัวตนตั้งค่าด้วย Devise
- บทบาทผู้ใช้ที่กำหนดสองบทบาทคือ "writer" และ "editor"
- โมเดล
Post
ด้วยสิ่งนี้ เรามีทุกอย่างที่ต้องการเพื่อทำงานกับ Pundit ได้อย่างเหมาะสม
การอนุมัติในแอป Ruby ด้วย Pundit
Pundit เป็นไลบรารีการอนุมัติที่สร้างขึ้นรอบสถาปัตยกรรมเชิงวัตถุและคลาส Ruby ธรรมดา มันให้เครื่องมือสร้างชั้นการอนุมัติที่มั่นคงซึ่งสามารถขยายได้พร้อมกับแอปของคุณ
การติดตั้ง Pundit ในแอป Ruby ของคุณ
เพิ่ม gem ลงใน Gemfile ของแอป:
จากนั้นในเทอร์มินัล รันคำสั่ง:
หรือรันคำสั่งต่อไปนี้:
เนื่องจากการอนุมัติส่วนใหญ่เกี่ยวข้องกับการให้หรือปฏิเสธการเข้าถึงทรัพยากรของ controller ขั้นตอนต่อไปคือการเพิ่มโมดูลการอนุมัติของ Pundit เข้าไปใน application controller:
และสุดท้าย สร้างคลาสนโยบายฐานที่นโยบายอื่นๆ จะสืบทอดมา:
ซึ่งให้คลาสนโยบายฐานดังนี้:
ด้วยสิ่งนี้ Pundit ตอนนี้ตั้งค่าพร้อมใช้งานแล้ว นอกจากนี้เรายังมีการรับรองตัวตนและบทบาทผู้ใช้ตั้งค่าไว้แล้ว
ต่อไป เราจะใช้นโยบายเพื่อใช้กฎที่กำหนดว่าแต่ละบทบาทผู้ใช้จะเข้าถึงทรัพยากร Post อย่างไร
การกำหนดค่านโยบาย Pundit
ในภาษาของ Pundit "นโยบาย" (policy) คือคลาส Ruby ธรรมดาที่คุณกำหนดกฎทั้งหมดสำหรับการที่บทบาทผู้ใช้โต้ตอบกับทรัพยากรต่างๆ
นโยบายเหล่านี้มีคุณสมบัติเด่นบางอย่าง:
- แต่ละนโยบายตั้งชื่อตามโมเดลที่มีอยู่ ต่อท้ายด้วยคำว่า "Policy" ตัวอย่างเช่น นโยบายที่กำหนดการเข้าถึงโมเดล
Postจะเรียกว่าPostPolicy attr_reader: นี้รับสองอาร์กิวเมนต์ - อันแรกคือuserโดยเฉพาะผู้ใช้ที่ล็อกอินอยู่ -current_user- และอาร์กิวเมนต์ที่สองคือโมเดลที่คุณต้องการกำหนดกฎการอนุมัติ ในกรณีนี้คือpost- เมธอดคำถามที่จะแมปไปยังเมธอดของ controller ของทรัพยากรที่ตั้งค่ากฎการอนุมัติไว้ เพื่อการจัดระเบียบ ควรวางนโยบายทั้งหมดไว้ภายใต้โฟลเดอร์
app/policies
เนื่องจากเรารู้กฎการเข้าถึงที่ต้องกำหนดสำหรับบทบาทผู้ใช้ต่างๆ ในแอปของเรา เริ่มต้นกับบทบาท writer กัน
การกำหนดนโยบาย Pundit สำหรับบทบาท
ลองใช้บทบาท writer เพื่อดูว่าสามารถทำได้อย่างไร เริ่มต้นเราสามารถวางแผนการเข้าถึงทรัพยากร Post ของบทบาท writer ได้ดังนี้:
- สร้าง โพสต์ของตัวเอง
- แก้ไขและอัปเดต โพสต์ของตัวเอง
- ดู (หรืออ่าน) โพสต์ของตัวเองและของผู้ใช้อื่น
- ลบ โพสต์ของตัวเอง
ด้วยความคิดนี้ ให้สร้างนโยบายใหม่เพื่อควบคุมการเข้าถึงโพสต์:
ซึ่งให้คลาสนโยบายทั่วไปที่สืบทอดมาจากนโยบายฐานที่เราสร้างก่อนหน้านี้:
ตอนนี้เพิ่มกฎการเข้าถึงสำหรับบทบาท writer ตามลำดับ (ซึ่งจะ override กฎที่สืบทอดมาจากคลาสนโยบายฐาน):
ที่นี่ เรากำหนดว่าผู้เขียนสามารถทำอะไรได้เมื่อสร้าง แก้ไข อัปเดต และลบโพสต์ซึ่งเป็นการดำเนินการที่สอดคล้องกับ controller ของโพสต์ หากคุณสังเกตเห็น กฎเหล่านี้ใช้กับโพสต์โดยทั่วไป不一定是โพสต์ของ writer คนนั้น (เราจะไปที่ส่วน scopes)
ตอนนี้ เรามาดูวิธีการใช้นโยบายนี้
การใช้นโยบายใน Pundit
ในการใช้นโยบาย ให้เรียกเมธอด authorize ของ Pundit บนเมธอดของ controller ที่คุณต้องการตรวจสอบกฎการเข้าถึง คุณสร้างอินสแตนซ์ของคลาสนโยบายที่เกี่ยวข้อง และเฉพาะเจาะจงการดำเนินการที่ควรเรียกตามที่เมธอด authorize ถูกเรียก
ตัวอย่าง ให้เรียก authorize บนเมธอด create ของ post controller:
เพื่อทดสอบ ล็อกอินเป็น editor และลอง create โพสต์ การทำเช่นนี้จะทำให้เกิดข้อผิดพลาดดังนี้:

แม้ว่าสิ่งนี้จะทำงานตามที่เราต้องการ แต่การแสดงหน้าข้อผิดพลาดแบบนี้ไม่ดีต่อประสบการณ์ผู้ใช้ ในส่วนต่อไป คุณจะเรียนรู้วิธีจับ NotAuthorizedError และแสดงหน้าที่เป็นมิตรกับผู้ใช้มากขึ้น
การจับข้อผิดพลาด NotAuthorizedError ของ Pundit
ก่อนอื่น เราต้องแก้ไข ApplicationController ดังนี้:
ที่นี่ เรากำลังบอก Pundit ว่าให้ใช้เมธอด user_not_authorized ทุกครั้งที่ NotAuthorizedError ถูกยกขึ้น มันจะเพียงแค่ redirect ผู้ใช้ที่ไม่ได้รับอนุญาตไปยังหน้าเฉพาะและให้ข้อความ flash ที่เกี่ยวข้องอธิบายสิ่งที่เกิดขึ้น:

ตอนนี้เรามีระบบการอนุมัติเรียบง่ายที่สามารถจัดการกรณีสิทธิ์ทั่วไปได้
แต่ถ้าเราต้องการสิทธิ์ที่ละเอียดกว่านี้? สำหรับสิ่งนี้ เราต้องใช้ scopes ของ Pundit
Scopes ของ Pundit
Scopes ของ Pundit คล้ายกับ scopes ของ ActiveRecord ใน ActiveRecord คุณสามารถใช้ scopes เพื่อดึงระคอร์ดตามเกณฑ์เฉพาะ แต่กับ scopes ของ Pundit คุณจัดการการเข้าถึงทรัพยากรเฉพาะตามกฎที่คุณตั้ง
สมมติว่าเราต้องการให้ editors สามารถดูและแก้ไขโพสต์ที่อยู่ในสถานะ "draft" และในเวลาเดียวกัน ให้ writers สร้าง ดู แก้ไข อัปเดต และลบโพสต์ที่เป็นของพวกเขาเท่านั้น
เราสามารถเริ่มต้นโดยแก้ไขนโยบายโพสต์ให้เป็นแบบนี้:
จากนั้นเราจะอนุมัติการเข้าถึงทรัพยากรใน posts controller เหมือนนี้:
แน่นอน scoping ด้วย Pundit สามารถลึกกว่านี้มาก แต่อยู่ที่นี้ก่อนในโพสต์นี้ หากต้องการ ดูเอกสาร Pundit เพื่อดูวิธีใช้ scopes แบบขั้นสูง
ตอนนี้ เรามาดูวิธีการใช้ไลบรารีกับ strong parameters ของ Rails
การใช้ Pundit กับ Strong Parameters ของ Rails
การรวมกฎการอนุมัติของ Pundit กับ strong parameters ของ Rails ช่วยให้คุณล็อกดาวน์การเข้าถึงแอตทริบิวต์ของทรัพยากรได้ สมมติว่าคุณต้องการให้ editors เท่านั้นที่เข้าถึงฟิลด์ excerpt ของโมเดล Post จะทำอย่างไร?
ก่อนอื่น เพิ่มบล็อกที่เหมาะสมในนโยบายที่เกี่ยวข้อง:
จากนั้นแก้ไขบล็อก permitted params ใน controller:
ด้วยสิ่งนี้ คุณทำให้แอตทริบิวต์ excerpt ใช้ได้เฉพาะ editors
เราจะเปลี่ยนหัวข้อไปดูไลบรารีการอนุมัติอีกตัวคือ CanCanCan
การแนะนำ CanCanCan สำหรับแอป Ruby ของคุณ
CanCanCan เป็นไลบรารีการอนุมัติที่ใช้คลาส "ability" เพื่อกำหนดว่าใครมีสิทธิ์เข้าถึงอะไรในแอป Rails การควบคุมการเข้าถึงจริงทำได้โดยใช้โมดูลการอนุมัติและ view helpers ต่างๆ
การติดตั้ง CanCanCan
การติดตั้งง่ายเพียงแค่รันคำสั่งด้านล่าง:
เหมือนกับ Pundit ด้วย CanCanCan คุณสามารถกำหนดกฎการเข้าถึงทั้งหมดภายในอ็อบเจกต์คลาส Ruby ธรรมดาที่เรียกว่า "ability class" ลองทำกันต่อไปกับแอป Rails ตัวอย่างของเรา
การกำหนดและตรวจสอบ Abilities ของ CanCanCan
เราจะใช้บทบาทผู้ใช้เหมือนตัวอย่าง Pundit: writers และ editors writer สามารถสร้าง แก้ไข อัปเดต ทำลาย โพสต์ของตัวเอง และดูโพสต์ของ writer คนอื่น editor สามารถทำทุกอย่างยกเว้นสร้างโพสต์ของตัวเอง
ในการใช้ CanCanCan ให้กำหนดก่อนว่าแต่ละผู้ใช้หรือบทบาทสามารถเข้าถึงอะไรได้ใน ability class ตามรูปแบบนี้:
ตัวอย่าง:
จากนั้นใน controller ให้ตรวจสอบว่ามีกฎการเข้าถึงสำหรับการดำเนินการเฉพาะหรือไม่ — ใช้ตัวอย่างของเรา คือ action edit:
ด้วยสิ่งนี้ ถ้าเราไปที่มุมมองแก้ไขโพสต์ในฐานะ writer อื่น เราจะได้ข้อผิดพลาดด้านล่าง:

และเหมือนที่เราทำกับ Pundit เราจะจับข้อผิดพลาดนี้และแสดงหน้าข้อผิดพลาดที่ดีกว่า
การจัดการข้อผิดพลาด "Access Denied" ของ CanCanCan
ทุกครั้งที่ทรัพยากรไม่ได้รับอนุญาต CanCanCan จะยกข้อผิดพลาด CanCan::AccessDenied วิธีที่ง่ายที่สุดในการจับข้อยกเว้นนี้คือการแก้ไข ApplicationController ดังนี้:
การทำเช่นนี้ทำให้ประสบการณ์ผู้ใช้ดีขึ้น ในภาพด้านล่าง ผู้ใช้ที่ไม่ได้รับอนุญาตถูก redirect ไปหน้าแรกและแสดงข้อความ flash ที่เกี่ยวข้อง:

คุณสามารถปรับแต่งข้อความข้อผิดพลาดที่แสดงให้ผู้ใช้ได้อีกด้วย:
หากแอปของคุณตอบสนองเป็น XML หรือคุณต้องการเจาะลึกการจัดการข้อยกเว้น CanCanCan::AccessDenied ดูเอกสาร CanCanCan ตอนนี้ เรามาดูวิธีรวม abilities ต่างๆ ของ CanCanCan เพื่อสร้างชั้นการอนุมัติที่แข็งแกร่งกว่า
การรวม Abilities หลายตัวของ CanCanCan
คุณสามารถกำหนดกฎการเข้าถึงหลายกฎสำหรับทรัพยากรใน ability class ได้ โดยใช้บทบาท writer และ editor ตัวอย่างเช่น เราสามารถทำได้ดังนี้:
คำถามคือ ทำไมคุณถึงต้องการทำเช่นนี้?
ด้วย CanCanCan คุณสามารถกำหนดกฎการเข้าถึงทั้งหมดในไฟล์ ability เดียว ซึ่งมีข้อดีและข้อเสีย
การมีกฎทั้งหมดในที่เดียวสะดวกมากสำหรับการจัดการกฎการเข้าถึง เนื่องจากกฎทั้งหมดถูกกำหนดไว้ในที่เดียว
อย่างไรก็ตาม หากแอปของคุณจัดการกับบทบาทผู้ใช้มาก หรือมีทรัพยากรหลายอย่างที่ต้องการการอนุมัติ คลาส ability อาจกลายใหญ่และซับซ้อนเกินไปจนจัดการไม่ออก วิธีหนึ่งในการจัดการคือการจัดระเบียบคลาส ability ใหม่เพื่อใช้การกำหนดเมธอด เหมือนนี้:
ยังมีอะไรเกี่ยวกับ CanCanCan มากกว่าที่จะครอบคลุมในบทความนี้ได้อย่างมีประสิทธิภาพ โปรดดูเอกสาร CanCanCan ที่ละเอียดเพื่อเรียนรู้เพิ่มเติม
เพื่อสรุป เรามาเปรียบเทียบคุณสมบัติของแต่ละไลบรารีและให้เหตุผลว่าทำไมคุณอาจเลือกตัวหนึ่งมากกว่าอีกตัว
การเปรียบเทียบคุณสมบัติ: Pundit vs CanCanCan สำหรับแอป Ruby ของคุณ
- การจัดระเบียบไฟล์ - ด้วย Pundit คุณสามารถจัดระเบียบการอนุมัติของแอปข้ามหลายไฟล์นโยบายได้ง่าย แต่กับ CanCanCan กฎการอนุมัติจะอยู่ในไฟล์ ability เดียว การทำงานกับไฟล์ ability หลายไฟล์ยังเป็นไปได้ แต่ไม่ใช่การใช้งานเริ่มต้น
- การทดสอบ - เนื่องจากโค้ดสิทธิ์ส่วนใหญ่จะอยู่ในคลาสเดียวเทียบกับหลายคลาส ability ของ Pundit การเขียนทดสอบสำหรับ CanCanCan อาจง่ายกว่า Pundit
- Helpers - ไลบรารีทั้งสองให้ view helpers จำนวนมากเพื่อตรวจสอบสิทธิ์ในชั้นมุมมอง CanCanCan ให้เมธอด
can?ใช้ใน views ส่วน Pundit ให้ฟังก์ชันคล้ายคลึงกับ helperpolicy - การรวมกับ Devise - ตามที่คุณเห็นจากตัวอย่างที่เราใช้ในบทความ ไลบรารีทั้งสองรวมกับ Devise ได้ดีมาก
สรุป
ในบทความนี้ เราได้ดูไลบรารีการอนุมัติที่ได้รับความนิยมในระบบนิเวศ Ruby และ Rails สองตัว: Pundit และ CanCanCan
ทั้งสองไลบรารีเสนอชุดคุณสมบัติที่หลากหลายสำหรับจัดการสิทธิ์ในแอป Rails ของคุณ เนื่องจากนี้ จึงเกือบจะเป็นไปไม่ได้ที่จะบอกคุณว่าควรเลือก gem ไหน — ทั้งคู่สามารถจัดการการตั้งค่าสิทธิ์ที่ซับซ้อนที่สุดได้
เราขอแนะนำให้คุณลองใช้ Pundit และ CanCanCan ในแอปของคุณและดูว่าตัวไหนเหมาะกับความต้องการของคุณที่สุด
ขอให้เขียนโค้ดสนุกสนาน!
P.S. หากคุณต้องการอ่านโพสต์ Ruby Magic ทันทีที่เผยแพร่ สมัครรับจดหมายข่าว Ruby Magic และจะไม่พลาดโพสต์ใดๆ!