RBAC กลาง — Model A vs Model B และแนวทางเปลี่ยนผ่าน¶
สรุปสถาปัตยกรรม role/permission ของ SSO กลาง (Keycloak realm
rlpd) ว่าโมเดลเดิม (A) กับโมเดลใหม่ (B) ต่างกันอย่างไร และแผนเปลี่ยนผ่านแบบ lean / additive-only ที่ไม่กระทบระบบเดิม — รายละเอียดการออกแบบเต็มอยู่ที่SSO_RBAC_CENTRAL_DESIGN.md(repoweb_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.<client>.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, ไม่กระทบของเดิม)¶
หลักคือ ไม่ย้ายอะไรที่วิ่งอยู่ — เติมของใหม่ให้ของเก่าหมดหน้าที่ไปเอง:
- Seed
AuthzRolesให้ครบทุกระบบที่มี KC client (INSERT อย่างเดียว) — dropdown ฟอร์มขอสิทธิ์เลือก catalog ใหม่ก่อน legacy โดยอัตโนมัติ และ flow อนุมัติ sync role เข้า KC ให้เอง (best-effort, ล้มเหลว = log + assign มือได้) - ไม่สร้าง client ใหม่ให้ RLPD002–007 ตอนนี้ — โมดูลใน portal ใช้ group ต่อไป ย้ายพร้อมวัน cutover production ทีเดียว
- ไม่ลบ ไม่แก้ 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 ครบทุกระบบ