API Restriction · 02 — ให้ endpoint บอกว่า "ต้องทำอะไรได้" ไม่ใช่ "ใครเรียกได้"
แผน: เลิกเขียนชื่อ app หรือชื่อ role ลงใน endpoint แล้วย้ายคำตอบว่าใครเรียกได้ไปอยู่ในตาราง Permission ที่มีอยู่แล้ว — app ใหม่มา plug-in แค่ insert row
อัปเดต: 2026-09-07
⬅️ ที่มาของงานนี้ → 00 — ปัญหา มติที่เสนอ และตัวเลือกที่ตกรอบ 🔗 อ่านคู่กัน → 01 — role ที่ผูกกับ app ที่เรียกจริง
02. ให้ endpoint บอกว่า “ต้องทำอะไรได้” ไม่ใช่ “ใครเรียกได้”
🔵 หน้านี้คือข้อเสนอ ยังไม่ใช่มติ และยังไม่มีโค้ด
ปัญหา — ดูจากตัวอย่างเดียว
/users/me อยู่ที่ User Service ฝั่ง Platform แต่ FX ก็เรียก และอนาคตจะมี app อื่นเรียกอีก
ถ้าเราเขียนแบบนี้:
[Authorize(Roles = "PlatformUser, FxUser")] // ❌
public async Task<IActionResult> GetMe() { ... }
พอมี app ที่ 3 เข้ามา ต้องกลับมาแก้บรรทัดนี้ แล้ว deploy ใหม่ · มี 10 endpoint แบบนี้ ก็แก้ 10 ที่ · มีอีก 13 service ก็ทวีคูณไปอีก
ไอเดีย — เปลี่ยนสิ่งที่เขียนบน endpoint
เขียนแบบนี้แทน:
[RequirePermission("users:self:read")] // ✅
public async Task<IActionResult> GetMe() { ... }
บรรทัดนี้แปลว่า “ใครก็ตามที่ทำ users:self:read ได้ เรียกเส้นนี้ได้” — ไม่พูดถึง app ไหน ไม่พูดถึง role ชื่ออะไร เขียนครั้งเดียวตอนสร้าง endpoint แล้วไม่ต้องกลับมาแก้อีก
คำตอบว่า “ใครทำได้บ้าง” ย้ายไปอยู่ใน ข้อมูล — ตาราง RolePermission ที่มีอยู่แล้วในระบบ
app ใหม่มา plug-in ต้องทำอะไร
สมมติมี app ใหม่ชื่อ Trade เข้ามา และต้องใช้ /users/me:
| เดิม | ใหม่ |
|---|---|
แก้ [Authorize(Roles = "…, TradeUser")] ในทุก endpoint ที่เกี่ยว | ไม่ต้องแตะโค้ด |
bump Backend_Package + redeploy service ที่เกี่ยว | ไม่ต้อง deploy |
| รอรอบ release | admin insert row ได้เลย |
สิ่งที่ admin ทำคือ: สร้าง App Trade → สร้าง Role TradeUser ใต้ AppId ของ Trade → ผูก RolePermission ชี้ไปที่ users:self:read ที่ Platform เป็นเจ้าของอยู่แล้ว
ไล่ทีละขั้นตอน
sequenceDiagram
autonumber
participant FE as หน้าจอ FX
participant MW as middleware กลาง
participant R as Redis UserInfo
participant EP as GET /users/me
FE->>MW: เรียก /users/me + header AppId = FX
MW->>R: ดึง UserInfo ของ user คนนี้
R-->>MW: apps ทั้งหมด พร้อม role และ permission ของแต่ละ app
MW->>MW: หยิบเฉพาะ app FX (ตามข้อ 1) ไม่เจอ ให้ 401
MW->>EP: ส่งต่อ พร้อม permission ของ user ใน FX
EP->>EP: เส้นนี้ต้องการ users:self:read ของเจ้าของคือ User Service
alt user มี users:self:read ที่เจ้าของตรงกัน
EP-->>FE: 200 พร้อมข้อมูล
else ไม่มี
EP-->>FE: 403
end
กติกา 3 ข้อที่ทำให้มันไม่รั่ว
1. permission ต้องมาจาก app ที่เลือกเท่านั้น ไม่ใช่รวมทุก app — ถ้าไม่ทำข้อนี้ Platform-Admin ที่เรียกผ่าน FX จะได้ permission ของ Platform ติดมาด้วย ซึ่งเป็นปัญหาเดิมที่แค่ย้ายลงไปอีกชั้น ⇒ ใน Redis blob ต้องเก็บ permission แยกใต้แต่ละ app ไม่ใช่กองรวมกันไว้ที่เดียว
2. ต้องรู้ว่า permission นั้น “ใครเป็นเจ้าของ” — ในระบบวันนี้ชื่อ permission unique แค่ต่อ AppId (PermissionsConfiguration.cs:73-75) แปลว่า users:self:read ของ Platform กับของ FX เป็นคนละแถวได้ ⇒ เวลาเทียบ ต้องเทียบทั้งชื่อและเจ้าของ
3. เจ้าของที่ endpoint ใช้เทียบ มาจาก config ของ service ไม่ใช่จาก header — ตรงนี้สำคัญ เพราะแปลว่าต่อให้ client ปลอม header มา ก็ขยับฝั่งขวาของการเทียบไม่ได้
ของที่มีอยู่แล้ว ไม่ต้องสร้างใหม่
ตรวจกับ Backend_UserService@25b1923 แล้ว:
- ตาราง
PermissionsและRolePermissionมีอยู่ ใช้งานอยู่ - ชื่อ permission ถูกประกอบเป็น
{resource}:{action}ให้อัตโนมัติที่Permissions.cs:33-56⇒ รูปแบบusers:self:readเป็นข้อตกลงที่ระบบใช้อยู่แล้ว ไม่ใช่ของที่เราคิดใหม่ - ✅ ที่สำคัญที่สุด: read path ปัจจุบันยอมให้ role ของ app หนึ่ง ผูกกับ permission ที่อีก app เป็นเจ้าของได้อยู่แล้ว —
GetRolePermMenuShellHandler.csfilter role ตาม AppId ที่เรียก แต่ตอนดึง permission ไม่ได้ filter ตามPermission.AppId⇒ ประโยค “app ใหม่ = insert row” เป็นจริงตามตัวอักษร ไม่ต้องแก้ query
ต้องเขียนอะไรเพิ่มบ้าง — 3 ชิ้น
[RequirePermission("…")]— attribute ตัวใหม่ในBackend_Package- ตัวตรวจ —
AuthorizationHandlerที่อ่าน permission จากHttpContext.GetUserInfo()(ข้อมูลที่ middleware ดึงมาแล้ว ไม่ต้องยิง DB ต่อ request) +IAuthorizationPolicyProviderเพื่อไม่ต้องประกาศ policy ทุกชื่อไว้ล่วงหน้า - เพิ่ม permission ลง Redis blob — แก้ที่
UsersMapper.ToUserInfoForRedisในBackend_UserService
🔴 การเพิ่ม field ต้องเป็นแบบ optional และห้ามแตะ CurrentSchemaVersion — repo นี้ทำแบบนี้มาแล้ว 4 ครั้ง (cardId, passportNo, custCode, amloStatus) และมี comment กำกับห้ามไว้ทุกจุด เหตุผลอยู่ใน 01
⏳ ข้อควรระวังตอน deploy: blob เก่าที่ยังอยู่ใน Redis จะไม่มี permission จนกว่าจะหมดอายุตาม TTL 86400 วินาที หรือถูก refresh ⇒ ถ้าเปิดใช้ทันทีหลัง deploy ทุกเส้นที่แปะ attribute จะ 403 พร้อมกัน ต้องเปิดผ่านช่วงสังเกตของ กลไก E ซึ่งมีเรื่องต้องแก้ก่อน ดู 03
ที่ยังไม่รู้ และกระทบการตัดสินใจตรง ๆ
- ตาราง
Permissions/RolePermissionบน dev มีข้อมูลจริงกี่แถว — เห็นแค่ schema กับโค้ดที่อ่านมัน ยังไม่ได้เห็นข้อมูล ถ้าตารางว่างหรือไม่มีใคร maintain แผนนี้จะไม่มีข้อมูลให้ใช้ - AppId (GUID) จริงของแต่ละ app และใครเป็นเจ้าของ
/users/me— ต้องรู้ก่อนถึงจะตั้ง config ในกติกาข้อ 3 ได้