跳至主要内容

Authority 權限模型設計

文檔版本: v3.1.0 最後更新: 2026-08-14 適用對象: 開發團隊、AI Agent、使用 app-tenant-server 範本的團隊 相關 ADR: ADR-009 雙模式 Resource ServerADR-013 委派與模擬ADR-018 auth 身分模型ADR-031 風險導向 API 授權

本文檔記錄參考實作的 Authority 權限模型:RBAC(Account → Role → Authority)搭配風險導向的 business capability、object/data scope 與 Audit。非敏感端點預設以 Authenticated + Audit 保護; Authority 只用於敏感資訊或敏感/高影響操作,命名採 domain:capability。既有 R/W/X/D 權限是歷史 inventory,不再是新增 domain 的設計模板。


目錄

  1. 設計概述
  2. Authority 命名規則
  3. 權限的宣告與入庫(分散宣告 + SPI)
  4. Role 定義
  5. Role → Authority 映射
  6. 實作指南
  7. 使用範例
  8. 管理面(業務層參考實作)

1. 設計概述

雙層權限模型

┌─────────────────────────────────────────────────────────────────────────┐
│ 前端(Role-based UI) │
│ • 使用 Role 控制選單、Applet、路由與一般操作 UI │
│ • login//me 的 user projection 只提供展開後的 roles │
│ • 不消費 raw Authority;特殊資源能力由 API 回 allowedActions │
│ • 前端判斷只用於 UX;真正的權限控制由後端 API 執行 │
└─────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────┐
│ 後端(API 層級) │
│ • 非敏感資訊/低風險操作:Authenticated + Audit │
│ • 敏感資訊/操作:Authority + 適用 data scope + Audit │
│ • Role 透過 seed 配置獲得對應的 Authority │
└─────────────────────────────────────────────────────────────────────────┘

核心原則

  1. 帳戶授予 Role - 每個帳戶分配一個或多個 Role(列表第一個為主要角色)
  2. Authority 有風險門檻 - 非敏感端點使用明確 authenticated policy + Audit;敏感資訊/操作才用 hasAuthority,後端不用 hasRole
  3. Role 授予 Authority - 透過 seed 配置(data/auth/Role.json),Role 獲得對應的 Authority
  4. 權限詞彙的 SoT 在後端 - 前端 mock(MSW)的權限映射是後端的衍生鏡像(見 ADR-009)
  5. Session UI projection 不含 Authority - login//me 的 user DTO 只回傳 roles[];Access Token 可保留 Authority claim 供 Resource Server 使用,產品前端不得解析它控制介面
  6. Capability 不機械映射 CRUD - 相同角色、風險與責任的操作先收斂;只有具體差異才拆分
  7. 保護按風險組合 - authentication 是底線,data scope 獨立適用;Audit 可作低風險端點的正式事後保護,敏感端點才加 capability

實體模型(RBAC,持久化於 DB)

Account ──多對多──> Role ──多對多──> Authority
實體位置說明
Account / AccountBasefeature/auth/帳號。canonical 出 AccountBase@MappedSuperclass),具體 Account 由各專案擁有(見 ADR-018)
Rolefeature/auth/Role.java角色。typeSYSTEM(內建、不可修改/刪除、tenantId 為 null)與 CUSTOM(租戶自訂)
Authorityfeature/auth/Authority.java業務能力,namedomain:capability 字串

帳號的有效權限 = 其所有角色的權限聯集(AccountBase.getAllAuthorities())。認證層實體繼承純稽核基類 AuditableBase、不走租戶隔離基類——登入必須在租戶已知之前運作。

權限載入走 fetch plan,不是 EAGER(改動前必讀)

Account.rolesRole.authorities 兩層關聯都是 LAZY。組裝 UserDetails 需要一次載完整個 graph,但那是查詢的需求、不是欄位的性質——由 repository 顯式宣告:

@EntityGraph(attributePaths = {"roles", "roles.authorities"})
Optional<Account> findWithRolesAndAuthoritiesById(UUID id);

不要把它改回 FetchType.EAGERentity-field-types 禁止),也不要靠 spring.jpa.open-in-viewUserDetailsServiceAuthPrincipalLookup 跑在認證鏈上、seed contributor 跑在 CommandLineRunner 上,兩者都沒有交易、也不在 HTTP 請求緒上,OSIV 對它們無效。

新增任何載入認證主體的入口時,一律用帶 @EntityGraph 的 finder;漏用的症狀是執行期 LazyInitializationException,且只在沒有 OSIV 的路徑上出現(走 MockMvc 的測試看不見)。參考實作以一支關掉 OSIV、直接呼叫服務的迴歸測試釘住此不變量。


2. Authority 命名規則

命名格式:domain:capability

{domain}:{capability}
  • domain:穩定的業務/feature 邊界,小寫 snake_case(如 applicationreference_data
  • capability:可授予的業務能力,小寫 snake_case(如 manageread_allexport

歷史備註:既有參考域仍有 {RESOURCE}_{R|W|X|D} 常數與 resource:read|write|execute|delete。 ADR-031 不要求未盤點消費者就批次改名;它要求新設計不再複製此模式,既有權限則經 capability review 與 revokedAuthorities migration 漸進收斂。

Capability 收斂詞彙

Capability適用情境不應使用的情境
manage相同角色、相近風險、同一管理流程中的讀寫與可逆操作delete、export 或敏感讀取有獨立責任
delete不可逆刪除或責任與一般管理不同可安全復原且與 manage 同責任的操作
read_all越過 ownership/一般資料範圍一般使用者只讀自己可見的資料
export大量或可攜出資料普通列表/單筆讀取
領域動詞approveresendrotate_secret 等風險或職責明確不同的操作只是在重述 HTTP method

這些是候選詞彙,不是每個 Entity 都必備的固定套餐。拆分前必須能指出角色、風險、資料敏感度或 職責分離的具體差異。

專屬 capability

Authority說明來源
notification_outbox:resend重送通知(觸發真實遞送)notification feature
impersonation:enter進入模擬(同租戶)impersonation feature(ADR-013)
impersonation:cross-tenant跨租戶模擬impersonation feature(ADR-013)

目前的 Resource 清單

下表是既有參考實作 inventory,不是新 domain 的產生模板;新設計先走本節的 capability review。

Resource擁有者readwriteexecutedelete其他
productsales 業務域
orderorder 業務域
customercustomer 業務域
reference_datareference-data feature
notification_templatenotification feature
notification_outboxnotification featureresend
impersonationimpersonation featureentercross-tenant
accountauthadmin/(業務層參考實作)execute=啟停/解鎖
roleauthadmin/(業務層參考實作)

授權設計流程

  1. Consumer/lifecycle:盤點 UI、外部 client、背景工作、workflow、framework replacement 與 obsolete/reference 來源,決定保留、取代或退役。
  2. Endpoint classification:標記 Public、Authenticated、Capability protected、Object scoped 或 System/operation only;非敏感端點優先 Authenticated + Audit,catch-all authenticated 不算明確分類。
  3. Risk:評估敏感度、可逆性、損害範圍、可偵測性、職責分離與跨組織/IDOR。
  4. Authority threshold:只有敏感資訊或敏感/高影響操作才進入 capability convergence;其餘不建 Authority。
  5. Data scope:把 owner、organization、assignee、creator 或 tenant policy 集中到 service/policy。
  6. Audit:Authenticated 端點必須有事後稽核決策;敏感端點同樣保留 Audit 作補償控制。
  7. Tests:建立 401/403/成功/跨 scope/public-state/Audit 矩陣。

API Spec 必須保存逐 endpoint 的授權設計矩陣;任一保留 endpoint 缺分類或必要 scope 時不得實作。


3. 權限的宣告與入庫(分散宣告 + SPI)

權限由擁有者宣告,不集中維護

權限名稱由擁有該資源的 feature 或業務領域宣告,不集中在 auth 的一份清單(早期的集中式 Authority.json / RoleAuthorityMapper 已廢除)。每個域有自己的權限常數類,作為常數與 seed 的單一事實來源:

常數類位置宣告的權限
SalesAuthoritysales/product:*
OrderAuthorityOrderSeedContributor 宣告)order/order:*
CustomerAuthorityCustomerSeedContributor 宣告)customer/customer:*
ReferenceDataAuthoritysecurity/referencedata/reference_data:*
NotificationAuthorityfeature/notification/notification_template:*notification_outbox:*
ImpersonationAuthorityImpersonationAuthorityContributor 宣告)feature/impersonation/impersonation:*

AuthorityContributor SPI(框架契約)

框架(io.leandev.appfuse.security.AuthorityContributor)開出貢獻點,各域實作它宣告自己的權限:

/// Authority 名稱的貢獻點。
public interface AuthorityContributor {

/// 本 feature/業務領域擁有的權限名稱(domain:capability 形式)。
List<String> authorityNames();
}

存在的理由是依賴方向:認證是必要能力,不得依賴任何選擇性 feature 或業務碼。未安裝的 feature 不貢獻權限,其權限自然不存在——角色連結階段對找不到的權限發 warn 並跳過,正是「feature 沒裝」的語意。契約只宣告「有哪些名字」,消費方式歸消費端——參考實作 seed 進 DB,其他消費端可改餵外部 IAM 或只做啟動期校驗。

入庫流程(AuthSeedContributor

AuthSeedContributor 開機時依序 seed:Authority → Role → Account

階段行為冪等策略
Authority收集所有 AuthorityContributor 宣告的權限名,DB 無則新建冪等補缺,不以 count()==0 為 gate——否則新增權限永遠進不了既有持久 DB,導致角色缺權限、端點 403
Roledata/auth/Role.json;補上 authorities,撤回 revokedAuthorities新增與撤回皆冪等;撤回只適用 seed 擁有的 SYSTEM role,CUSTOM role/人工授權不動。找不到的權限 warn 並跳過
Accountdata/bootstrap/auth/Account.json 建立 structural 特權帳號;每個帳號各自產生隨機 UUID 密碼,成功寫入後以 WARN 輸出完整初始密碼first-install snapshot:僅在 Account table 全空時安裝整份檔案;既有帳號不重設密碼,也不再次輸出

初始密碼不從設定檔或環境變數讀取。操作者須在首次啟動時從受保護的啟動 log 取得密碼, 登入後立即變更;若錯過該次輸出,重新啟動不會再次顯示,應由既有具帳號管理權限的管理者 執行密碼重設。tenant 變體預設建立 superadminadmin,tenantless 變體預設只建立 admin

Demo 帳號是另一條明確啟用的資料路徑:僅在 app.seed.demo.enabled=true 時讀取 data/demo/auth/Account.json。該檔只宣告 structural 帳號以外的業務示範帳號,每筆必須 明確提供密碼;參考實作統一使用 Password123!,方便直接操作 demo。缺少或空白密碼視為 seed 錯誤,不以隨機值退回。固定 demo 密碼不適用於 production,production 不應啟用 demo seed。


4. Role 定義

結構角色與示範業務角色(type: SYSTEM

Role.name 存 DB 時不帶 ROLE_ 前綴;輸出給前端契約(login / /me 回應)時補上前綴。

Role顯示名稱說明
SUPER_ADMIN超級管理員平台/跨租戶能力(含 impersonation:cross-tenant),不單獨承載 tenant applet 權限
USER使用者tenant 範圍內的完整 applet 權限;權限模型尚未細分時所有一般帳號均使用此角色
MANAGER店長既有 demo 的完整業務權限;其中 CRUD authority 屬待遷移的 legacy inventory
SALES銷售人員訂單與客戶管理
FLORIST花藝師製作流程(訂單狀態變更)
DELIVERY配送員配送流程(訂單狀態變更)
ACCOUNTANT會計唯讀

租戶可另建 type: CUSTOM 的自訂角色(如 STORE_MANAGERCASHIER),由租戶管理、可修改/刪除。 MANAGERSALES 等角色屬 demo domain;建立真實權限模型時可替換。

角色階層(RoleHierarchyService

後端是角色階層的 single source of truth:

ROLE_USER
├── ROLE_MANAGER
│ ├── ROLE_SALES
│ └── ROLE_ACCOUNTANT
├── ROLE_FLORIST
└── ROLE_DELIVERY

SUPER_ADMINUSER 刻意不畫階層邊:前者代表平台範圍,後者代表 tenant applet 能力。 Bootstrap 平台帳號直接指派 [SUPER_ADMIN, USER],一般 tenant 帳號只指派 [USER]

兩個關鍵設計決定:

  1. 階層只用於「回應的角色陣列展開」:帳號在 DB 只指派直接角色(如 manager 帳號 = [MANAGER]);RoleHierarchyService 在 login / /me 回應中把直接角色展開為「主角色置首 + 所有可達角色」的扁平陣列(如 MANAGER 也算 SALES / ACCOUNTANT),供前端 RoleGuard 判定。主角色置首是契約的一部分——前端以 roles[0] 推導主要角色。
  2. 刻意不註冊為 Spring Security 的 RoleHierarchy bean:後端授權一律走 hasAuthority(domain:capability)Role.json 已把階層攤平進各角色的權限集;階層若進授權層會產生第二套語意。

前端 mock(types/auth.tsROLE_HIERARCHY)鏡像本階層定義,使真後端輸出與 Prototype 的 MSW mock 一致,整合階段 guard 行為不分歧。

攤平不變量的機械守護:階層邊是 UX 能力承諾(宣告 A > B 即「A 可當 B 用」,guard 會放 A 進 B 的 applet),後端授權卻只看攤平後的權限集——兩者一致與否由 RoleHierarchyAuthorityConsistencyTest 於 build 時檢查:每條已宣告的階層邊,父角色權限集必須 ⊇ 可達子角色的權限集。紅掉時二擇一:補權限進父角色(兌現承諾)或刪除該階層邊(收回承諾——職務分離等「上級不得代行」情境本就該用不畫邊表達)。此檢查使「前端 guard 放行、後端 API 403」的攤平漂移在 build 時現形,而非在使用者面前。


5. Role → Authority 映射

映射的 SoT 是 seed 檔 src/main/resources/data/auth/Role.json(角色階層已攤平進各角色的權限集):

業務資源映射表

Roleproductordercustomer
SUPER_ADMIN---
USERR W X DR W X DR W X D
MANAGERR W X DR W X DR W X D
SALESRR W XR W
FLORISTRR XR
DELIVERY-R X-
ACCOUNTANTRRR

Feature 資源映射表

Rolereference_datanotification_templatenotification_outboximpersonation
SUPER_ADMIN---cross-tenant
USERR W DR W DR + resendenter
其他角色----

詳細說明

SALES(銷售人員)

  • product:read - 查看商品(銷售時需要)
  • order:read / order:write / order:execute - 查看/新增/修改訂單、確認訂單
  • customer:read / customer:write - 查看/新增/修改客戶

FLORIST(花藝師)

  • product:read - 查看商品(設計時參考)
  • order:read / order:execute - 查看訂單、變更設計相關狀態(開始設計、完成設計)
  • customer:read - 查看客戶(了解需求)
  • 注意:沒有 order:write,不能修改訂單內容

DELIVERY(配送員)

  • order:read / order:execute - 查看訂單、變更配送相關狀態(開始配送、確認簽收)
  • 注意:沒有 product:*customer:*

ACCOUNTANT(會計)

  • 僅有 *:read(查看權限),只能查看數據,不能修改

6. 實作指南

6.1 權限常數定義(各域自帶)

位置: 擁有該 domain 的 package。

/// `announcement` 領域的 capability 常數(見 ADR-031)。
///
/// 由該領域的 AuthorityContributor 宣告入庫——常數與 seed 的單一事實來源在此。
public final class AnnouncementAuthority {

private AnnouncementAuthority() {}

/** 同一敏感管理責任下的建立、編輯與發布。 */
public static final String MANAGE = "announcement:manage";

/** 不可逆刪除由獨立責任保護。 */
public static final String DELETE = "announcement:delete";
}

6.2 權限宣告入庫(SPI 實作)

該域的 SeedContributor 同時實作 AuthorityContributor

@Component
public class AnnouncementSeedContributor implements SeedContributor, AuthorityContributor {

@Override
public List<String> authorityNames() {
return List.of(MANAGE, DELETE);
}
// ... 商品 seed 資料 ...
}

選擇性 feature 則用獨立的 provider(未安裝時 bean 不存在、權限不入庫):

/// 模擬權限——impersonation feature 自帶(見 ADR-013)。
@Component
public class ImpersonationAuthorityContributor implements AuthorityContributor {

@Override
public List<String> authorityNames() {
return List.of("impersonation:enter", "impersonation:cross-tenant");
}
}

6.3 Role → Authority 映射(seed JSON)

位置: src/main/resources/data/auth/Role.json

[
{
"name": "CONTENT_ADMIN",
"displayName": "Content Admin",
"displayNameZh": "內容管理員",
"description": "Manages public content",
"type": "SYSTEM",
"authorities": [
"announcement:manage",
"announcement:delete"
],
"revokedAuthorities": [
"announcement:read",
"announcement:write"
]
}
// ... 其他角色 ...
]

6.4 SecurityConfig 配置

@Configuration
@EnableMethodSecurity // 啟用 @PreAuthorize
public class SecurityConfig {
// RS 核心採標準 oauth2ResourceServer().jwt(),兩模式共用 @PreAuthorize 授權,
// 差異只在 token 來源——由 app.security.auth.mode(standalone / federated)切換
}

權限如何進入請求(見 ADR-009):

  • standalone 模式:登入時 JwtTokenProvider 把帳號的權限(角色權限聯集)寫入自簽 JWT 的 auth claimStandaloneJwtAuthenticationConverter 直接讀該 claim 還原 GrantedAuthority——無狀態、不回查 DB(權限變更於 token 換發時生效)
  • federated 模式:IdP token 的 scope 經 ScopeJwtAuthenticationConverter 對映到同一套 domain:capability

6.5 Controller 使用

依 classification 使用明確 authenticated 或 Authority 表達式;需要 capability 時以常數組合,避免手打字串漂移:

import static io.leandev.app.announcement.AnnouncementAuthority.*;

@RestController
@RequestMapping("/api/v1/announcements")
public class AnnouncementController {

@GetMapping
@PreAuthorize("isAuthenticated()")
public ResponseEntity<List<Announcement>> list() { ... }

@PostMapping
@PreAuthorize("hasAuthority('" + MANAGE + "')")
public ResponseEntity<Announcement> create(@RequestBody Announcement command) { ... }

@DeleteMapping("/{id}")
@PreAuthorize("hasAuthority('" + DELETE + "')")
public ResponseEntity<Void> delete(@PathVariable String id) { ... }
}

上例假設列表內容非敏感,因此使用明確 authenticated policy,service 仍須記錄安全 Audit;建立/刪除 公開內容因責任與影響較高才使用 Authority。若一般使用者讀取自己擁有的非敏感文件,可用 Authenticated + owner/組織/assignee scoped lookup + Audit,不必建立 document:read;只有敏感文件才 增加該 capability。具 document:read_all 才能越過一般資料範圍。Public 端點則由明確 permitAll matcher 擁有,repository 查詢直接帶 published/expiry/revoked predicate。

6.6 新增或審查一個 API domain 的 checklist

  1. 盤點 consumer 與生命週期,無消費者/已有替代 feature 時先退役。
  2. 在 API Spec 完成 endpoint security classification 與 risk matrix。
  3. 先判斷是否敏感;非敏感端點採 Authenticated + Audit,只有跨過門檻才收斂最小 capability。
  4. 對 Object scoped endpoint 定義 service/policy 層的資料範圍與 403/404 masking。
  5. 對 Public endpoint 明確宣告 permitAll,並把公開狀態 predicate 放進查詢。
  6. 為 Authenticated 端點及敏感操作決定 Audit event 與安全 payload。
  7. {Domain}Authority + AuthorityContributor 宣告 capability,Role seed 連結新權限;改名/收斂時 同步填 revokedAuthorities
  8. Controller annotation 或 SecurityContributor matcher 必須讓每個 endpoint 都有明確 policy owner。
  9. 自動化 401/403/成功/跨 scope/public state/Audit 矩陣。
  10. 前端只鏡像 mock token 所需 Authority;產品 UI 仍以 Role 或 API 回傳的 allowedActions 判斷。

7. 使用範例

7.1 訂單狀態變更的權限控制

@RestController
@RequestMapping("/api/v1/orders")
public class OrderController {

// 確認訂單 - SALES 可執行
@PatchMapping("/{id}/confirm")
@PreAuthorize("hasAuthority('" + ORDER_X + "')") // order:execute
public ResponseEntity<Order> confirmOrder(@PathVariable String id) { ... }

// 開始/完成設計 - FLORIST 可執行
@PatchMapping("/{id}/start-production")
@PreAuthorize("hasAuthority('" + ORDER_X + "')")
public ResponseEntity<Order> startProduction(@PathVariable String id) { ... }

// 開始配送/確認簽收 - DELIVERY 可執行
@PatchMapping("/{id}/start-delivery")
@PreAuthorize("hasAuthority('" + ORDER_X + "')")
public ResponseEntity<Order> startDelivery(@PathVariable String id) { ... }

// 修改訂單內容 - 需要 order:write(SALES 以上)
@PatchMapping("/{id}")
@PreAuthorize("hasAuthority('" + ORDER_W + "')")
public ResponseEntity<Order> update(@PathVariable String id, @RequestBody Order order) { ... }
}

7.2 前端 Applet 入口守衛(RoleGuard)

// 路由層以 Role 控制 Applet 入口;後端回應的 roles 已依階層展開為扁平陣列
<RoleGuard allowedRoles={['ROLE_SALES', 'ROLE_MANAGER']}>
<OrdersApplet />
</RoleGuard>
  • 前端權限檢查僅用於 UX,真正的權限控制由後端 API 執行
  • 例如店長登入後 roles = [ROLE_MANAGER, ROLE_SALES, ROLE_ACCOUNTANT, ...],與 allowedRoles 有交集即放行

7.3 特殊資源的操作能力投影

一般 UI 只使用 Role。只有 Role 無法表達資源狀態、資料範圍或其他動態政策,且 SBE 明確需要預先提示時,資源 API 才回傳貼近畫面語意的操作能力:

{
"id": "order-123",
"status": "CONFIRMED",
"allowedActions": ["cancel", "refund"]
}
  • allowedActionscapabilities 由後端針對該資源計算,可同時納入 Authority、資料範圍與狀態
  • 值使用 UI/業務操作詞彙,不直接輸出全域 domain:capability Authority 清單
  • 它只改善 UX;執行操作時對應 API 仍須重新授權與驗證狀態
  • 不需要這類提示的畫面直接呼叫 API,並統一處理 403

7.4 前端收到 403 的處理

// API 攔截器
apiClient.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response?.status === 403) {
showToast('您沒有權限執行此操作', 'error');
}
return Promise.reject(error);
}
);

8. 管理面(業務層參考實作)

本節前述的模型(帳號、角色、權限、映射)如何被管理,由業務層參考實作{controller|service|security}/authadmin/)提供參考端點面(ADR-022;原為 optional feature auth-admin,adoption 後 fleet 回饋顯示帳號來源異質、管理面分歧擴大,降級為 @reference-surface 生命週期——消費端依自己的帳號管理模式 retarget,或整域 prune)。

端點面

面向端點權限
帳號 CRUDGET/POST /api/v1/accountsGET/PATCH/DELETE /{id}account:read / account:write / account:delete
帳號生命週期POST /{id}/activate/{id}/deactivate/{id}/unlockaccount:execute
密碼管理PATCH /{id}/password(admin 重設,不驗舊密碼)account:write
角色指派PATCH /{id}/roles(全量替換;首位=主要角色)account:write
角色管理GET/POST /api/v1/rolesGET/PATCH/DELETE /{id}PATCH /{id}/authoritiesrole:read / role:write / role:delete
角色成員GET/PATCH /{id}/members(角色視角的反向指派)role:read / role:write
權限清單GET /api/v1/roles/authorities(供角色編輯選單)role:read

關鍵語意

  • SYSTEM 內建角色唯讀:修改/刪除回 409;只有 CUSTOM 自訂角色可 CRUD。自訂角色被帳號引用時不可刪(409)。
  • SUPER_ADMIN 成員關係受保護RoleMembershipPolicy 集中守住帳號角色與角色成員兩條異動路徑;只有目前已具 ROLE_SUPER_ADMIN 的操作者能新增或移除 SUPER_ADMIN 成員。專案若新增其他可提升管理範圍的 SYSTEM role,retarget 時應一併納入。
  • 租戶邊界走政策 SPIAccountAdminPolicy):tenant 模式下 USER 依目前 TenantContext 限本租戶;缺少 tenant context 時 fail closed。SUPER_ADMIN 是唯一 root scope,可跨租戶。範圍外的資源一律 404(不洩漏存在性)。
  • 角色權限禁止向上授予:非 SUPER_ADMIN 的角色管理者只能查看與指派自己目前持有的 authority;SUPER_ADMIN 可管理完整 authority catalog。SUPER_ADMIN 成員關係仍由 RoleMembershipPolicy 額外保護。
  • service 收 command+customizer、controller 承載形狀:per-project 擴充欄位與 @Version 樂觀鎖往返(參考實作的 Account 已 opt-in)都落在 controller 層(原 ADR-020 D-B/D-D 的設計,作為參考實作內部分工保留)。
  • 權限 account:*role:*業務層參考實作security/authadmin/,ADR-022 自 auth-admin feature 降級)的 AuthorityContributor 宣告入庫,Role.json 連結 USER

附錄:設計決策記錄

日期決策原因
2025-12-23採用 R/W/X/D Authority 模型細粒度權限控制、類似 Linux 概念易理解
2025-12-23X(execute)用於狀態變更,W(write)用於資料修改區分「執行操作」和「修改內容」的權限層級
2025-12-23D(delete)獨立於 W刪除是較危險的操作,需要獨立控制
2026-06權限詞彙收斂為 resource:actionADR-009standalone / federated 兩模式對映到同一套詞彙;SoT 在後端,前端 mock 為衍生鏡像
2026-06廢除集中式權限清單,改為分散宣告 + 貢獻點 SPI(時名 AuthoritySeedProvider;2026-07 上收框架並改名 AuthorityContributor權限由擁有該資源的 feature/業務域宣告;auth(必要 feature)不依賴選擇性 feature,未安裝的 feature 權限自然不存在
2026-06角色階層由 RoleHierarchyService 承擔、僅用於回應展開(FU-29)授權層單一語意(只走 hasAuthority,Role.json 攤平階層);前端 guard 拿到展開後的扁平 roles 陣列,與 MSW mock 一致
2026-07Account 降為應用宣告的 seam 實體(ADR-018canonical 出 AccountBase,具體 Account 由各專案擁有,正當客製不再與同步互斥
2026-07-20管理面收編為 optional auth-admin feature(ADR-020fleet 兩家下游各自重造帳號/角色管理端點;service canonical+controller seam、政策 SPI 隔離中性、參考 Account@Version opt-in
2026-08-14採用風險導向 capability + object scope + Audit(ADR-031Authority 不再依 Entity × CRUD 膨脹;公開狀態、資料範圍與 seed 撤權有可驗證契約
2026-08-14非敏感端點改採 Authenticated + AuditAuthority 只保護敏感資訊與操作,以事後稽核降低權限數量與管理複雜度

文檔維護者: Development Team + AI Assistant 最後審閱: 2026-08-14