ข้ามไปที่เนื้อหา

RBAC กลาง — Model A vs Model B และแนวทางเปลี่ยนผ่าน

สรุปสถาปัตยกรรม role/permission ของ SSO กลาง (Keycloak realm rlpd) ว่าโมเดลเดิม (A) กับโมเดลใหม่ (B) ต่างกันอย่างไร และแผนเปลี่ยนผ่านแบบ lean / additive-only ที่ไม่กระทบระบบเดิม — รายละเอียดการออกแบบเต็มอยู่ที่ SSO_RBAC_CENTRAL_DESIGN.md (repo web_portal) และ inventory role จริงทุกระบบอยู่ที่ RBAC_ROLES_INVENTORY.md

ใจความเดียวที่ต้องจำ

Model A เก็บ "ตำแหน่ง" ไว้ใน Keycloak group แล้วให้แต่ละ app ตีความเอง / Model B เก็บ "role จริงของระบบ" ไว้ใน token แล้วความหมายอยู่ที่ matrix กลาง

Model A — แบบเดิม (production ปัจจุบัน)

สิทธิ์เก็บเป็น group path 3 ระดับ /<ระบบ>/<ระดับ> เช่น /RLPD003/Admin — ไม่มี client role เลยทั้ง realm

flowchart LR
    U["User: somchai<br/>group /RLPD003/Admin<br/>group /RLPD005/User"] -->|login| T["Token<br/>claim <code>groups</code>"]
    T --> S3["S3: โค้ด if(Admin)<br/>→ เปิดเมนูครบ"]
    T --> S5["S5: ตาราง Role_Menu<br/>ของตัวเอง"]
    T --> S8["S8: ตาราง Get_Role<br/>ของตัวเอง"]

ลักษณะสำคัญ:

  • role มีแค่แกนมาตรฐาน (SuperAdmin / Admin / User / Citizen) — ไม่ใช่ชื่อตำแหน่งจริงของงาน
  • "Admin ทำอะไรได้" hardcode ในโค้ดแต่ละ app + ตาราง ACL ภายในของใครของมัน
  • เพิ่ม/แก้ role = สร้าง group ใหม่ + แก้โค้ด + deploy ใหม่ทุก app ที่เกี่ยว
  • ไม่มีจุดไหนตอบได้ว่า "user คนนี้ทำอะไรได้บ้างทั้ง 9 ระบบ"

Model B — แบบใหม่ (UAT .155 ใช้แล้ว, ตัดสินใจ 29 ก.ค. 2569)

แต่ละระบบมี KC client ของตัวเอง และ role เป็น client role = ชื่อตำแหน่งจริง (consultant, supervisor, mediator, …) ส่วน "role นั้นทำอะไรได้" อยู่ใน matrix กลาง PORTAL.AuthzPermissions แก้ผ่านหน้า RBAC Admin ของ S9 ได้โดยไม่ต้อง deploy

flowchart LR
    U["User: somchai<br/>client role lcs→consultant<br/>client role pjos→supervisor<br/>group /Provinces/10"] -->|login| T["Token<br/><code>resource_access.&lt;client&gt;.roles</code>"]
    T --> APP["ระบบปลายทาง<br/>อ่าน role จาก token"]
    APP --> M[("Matrix กลาง<br/>PORTAL.AuthzPermissions<br/>Role × Resource × Action")]
    ADMIN["S9 RBAC Admin<br/>(SuperAdminIT)"] -->|แก้ได้ทันที<br/>ไม่ต้อง deploy| M

ตัวอย่าง matrix:

SystemCode RoleKey Resource Action
RLPD002 consultant my-cases view
RLPD002 consultant qa manage
RLPD002 supervisor assign manage

ลักษณะสำคัญ:

  • ความหมายของ role ย้ายจากโค้ดมาอยู่ในข้อมูล — แก้สิทธิ์ = แก้ matrix มีผลทันที
  • จังหวัด/เขตรับผิดชอบแยกเป็นแกนของตัวเอง: group /Provinces/<MoiCode> (0 = ส่วนกลาง) ไม่ปนกับ role
  • จัดการที่เดียว (S9: RBAC Admin + ฟอร์มขอสิทธิ์ + อนุมัติรายระบบ) พร้อม audit log ครบ
  • ระบบ legacy (S5–S8, OCIPA) ต่อเข้า Model B ผ่าน view + JIT sync ตอน login แล้ว

เปรียบเทียบสั้น ๆ

Model A (เดิม) Model B (ใหม่)
ที่เก็บสิทธิ์ KC group /<ระบบ>/<ระดับ> KC client role ต่อระบบ
ชื่อ role แกนมาตรฐาน 4 ระดับ ตำแหน่งจริงของแต่ละระบบ
ความหมายของ role hardcode ในโค้ดแต่ละ app matrix กลาง AuthzPermissions
แก้สิทธิ์ แก้โค้ด + deploy แก้ matrix ในหน้าจอ มีผลทันที
จังหวัด ปนอยู่ในตรรกะแต่ละ app group /Provinces/<code> แกนแยก
ภาพรวมทั้ง 9 ระบบ ไม่มี S9 เห็นจุดเดียว + audit

อยู่ร่วมกันได้ใน token ใบเดียว (หัวใจของแผน lean)

Token จาก realm เดียวกันมีทั้ง claim groups (Model A) และ resource_access (Model B) พร้อมกัน — สองโมเดลจึงวิ่งคู่กันได้โดยไม่เหยียบกัน:

flowchart TD
    L["Login ครั้งเดียว — token เดียว<br/>มีทั้ง groups และ resource_access"]
    L --> A["RLPD002–007 (โมดูลใน portal)<br/>ยังเดิน Model A (group) ตามเดิม"]
    L --> B["S1–S4 + legacy ที่ onboard แล้ว<br/>เดิน Model B (client role + matrix)"]
    L --> C["ประชาชน<br/>group /RLPD001/ประชาชน ตลอดไป<br/>(ไม่มี client role รายระบบ)"]

แนวทางเปลี่ยนผ่านแบบ lean (additive-only, ไม่กระทบของเดิม)

หลักคือ ไม่ย้ายอะไรที่วิ่งอยู่ — เติมของใหม่ให้ของเก่าหมดหน้าที่ไปเอง:

  1. Seed AuthzRoles ให้ครบทุกระบบที่มี KC client (INSERT อย่างเดียว) — dropdown ฟอร์มขอสิทธิ์เลือก catalog ใหม่ก่อน legacy โดยอัตโนมัติ และ flow อนุมัติ sync role เข้า KC ให้เอง (best-effort, ล้มเหลว = log + assign มือได้)
  2. ไม่สร้าง client ใหม่ให้ RLPD002–007 ตอนนี้ — โมดูลใน portal ใช้ group ต่อไป ย้ายพร้อมวัน cutover production ทีเดียว
  3. ไม่ลบ ไม่แก้ schema ใด ๆ — ตาราง legacy (ModuleRoles, UserRole, …) และ group-read ทิ้งไว้เป็น fallback/history ตามกฎเหล็ก additive-only

การ deploy และ rollback

Deploy แยกก้อนตาม phase: ระบบใหม่ขึ้นคู่ขนานกับของเดิม → DB/Keycloak เติม แบบ additive (migration ทุกตัวมี *_rollback.sql แบบ no-op ห้าม DROP) → สลับ nginx route ชี้มาระบบใหม่เมื่อพร้อม

Rollback = ชี้ nginx กลับ app เก่าเท่านั้น — ไม่ต้องถอน DB/Keycloak เพราะของที่เติมไว้ไม่ทำร้าย app เดิม (app เดิมไม่รู้จักและไม่แตะของใหม่)

สิ่งที่คงไว้ถาวร (ไม่ใช่ "ของเดิมที่ต้องตัด")

  • ประชาชน: group /RLPD001/ประชาชน ผ่าน ThaiD JIT mapper
  • จังหวัด: group /Provinces/<MoiCode>
  • สิทธิ์ผู้ดูแลระบบกลาง: group /SuperAdminIT

กติกา: ระบบใหม่ = client role เท่านั้น ห้ามมี group

ห้ามสร้างหรือ assign group /RLPDxxx/<tier> ให้ระบบที่มี SSO client — verify แล้ว (15 ส.ค. 2569) ว่า realm UAT มี group เฉพาะ 7 ระบบเดิม (RLPD001–007) และการ์ด portal ของระบบใหม่แสดงจาก client role ล้วนอยู่แล้ว

เหตุผล: ใน dual-read group ชนะ client role — ถ้าใครเผลอสร้าง group ให้ระบบใหม่ การ์ดจะแสดงจาก group แต่สิทธิ์จริงในระบบมาจาก client role เกิดสองแหล่งความจริงที่ drift จากกันได้ทันที กติกานี้ทำให้ระบบใหม่มี แหล่งสิทธิ์เดียวตลอดสาย (token → matrix) โดยไม่ต้องลบอะไรเลย

ขั้นสุดท้าย (ทำหลัง cutover production เท่านั้น)

Production realm ปัจจุบันยังเป็น Model A ล้วน — วัน cutover ต้อง seed client role ใน realm prod ให้ครบก่อน จากนั้นเปิด flag ให้ portal อ่าน client role (PERMISSION_ROLES_FROM_CLIENT_ROLES=true) โดยคง group-read เป็น safety net แล้วค่อยพิจารณาตัด group-read เมื่อ verify ครบทุกระบบ