跳至主要内容

Authority 權限模型設計

文檔版本: v1.0.0 最後更新: 2025-12-23 適用對象: 開發團隊、AI Agent

本文檔記錄 app-server 專案的 Authority 權限模型設計,採用類似 Linux 檔案系統的 R/W/X/D 概念。


目錄

  1. 設計概述
  2. Authority 命名規則
  3. Resource 定義
  4. Role 定義
  5. Role → Authority 映射
  6. 實作指南
  7. 使用範例

1. 設計概述

雙層權限模型

┌─────────────────────────────────────────────────────────────────────────┐
│ 前端(Applet 層級) │
│ • 使用 Role 控制 Applet 入口 │
│ • RoleGuard 是唯一的前端權限檢查點 │
│ • 進入 Applet 後所有功能都可用 │
└─────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────┐
│ 後端(API 層級) │
│ • 使用 Authority 控制 API 操作權限 │
│ • @PreAuthorize 檢查細粒度權限 │
│ • Role 透過映射獲得對應的 Authority │
└─────────────────────────────────────────────────────────────────────────┘

核心原則

  1. 帳戶授予 Role - 每個帳戶分配一個或多個 Role
  2. 方法使用 Authority 管制 - API 方法使用 @PreAuthorize 檢查 Authority
  3. Role 授予 Authority - 透過映射配置,Role 自動獲得對應的 Authority

2. Authority 命名規則

採用類似 Linux 檔案系統的權限概念:

權限縮寫說明範例操作
ReadR查看資料列表、詳情、搜尋
WriteW新增/修改資料內容創建、編輯
ExecuteX變更狀態(較小權限)確認訂單、完成設計、開始配送
DeleteD刪除資料刪除、取消

命名格式

{RESOURCE}_{PERMISSION}

範例

  • PRODUCT_R - 查看商品
  • ORDER_W - 新增/修改訂單
  • ORDER_X - 變更訂單狀態
  • CUSTOMER_D - 刪除客戶

X vs W 的區別

權限用途範例
W (Write)修改資料內容(較大權限)修改訂單金額、修改客戶資料
X (Execute)變更狀態(較小權限)確認訂單、完成設計、開始配送

設計考量

  • 設計師可以「執行」設計完成(X),但不能修改訂單內容(W)
  • 配送員可以「執行」簽收確認(X),但不能修改訂單(W)

3. Resource 定義

目前系統定義的資源:

Resource說明RWXD
PRODUCT商品查看商品新增/修改商品上架/下架刪除商品
ORDER訂單查看訂單新增/修改訂單狀態變更取消訂單
CUSTOMER客戶查看客戶新增/修改客戶變更客戶狀態刪除客戶

未來擴展

Resource說明
REPORT報表
EMPLOYEE員工(人事)
FINANCE財務
SETTING系統設定

4. Role 定義

採用 Spring Security 格式(ROLE_ 前綴):

Role說明
ROLE_ADMIN系統管理員
ROLE_OWNER店主
ROLE_MANAGER店長
ROLE_SALES銷售人員
ROLE_ACCOUNTANT會計
ROLE_PURCHASER採購
ROLE_FLORIST花藝師
ROLE_DELIVERY配送員

角色繼承

ROLE_ADMIN
└── ROLE_OWNER
├── ROLE_MANAGER
│ ├── ROLE_SALES
│ └── ROLE_ACCOUNTANT
├── ROLE_PURCHASER
├── ROLE_FLORIST
└── ROLE_DELIVERY

5. Role → Authority 映射

完整映射表

RolePRODUCTORDERCUSTOMER
ADMINR W X DR W X DR W X D
OWNERR W X DR W X DR W X D
MANAGERR W X DR W X DR W X D
SALESRR W XR W
ACCOUNTANTRRR
PURCHASERR W XRR
FLORISTRR XR
DELIVERY-R X-

詳細說明

ROLE_SALES(銷售人員)

  • PRODUCT_R - 查看商品(銷售時需要)
  • ORDER_R, ORDER_W, ORDER_X - 查看/新增/修改訂單、確認訂單
  • CUSTOMER_R, CUSTOMER_W - 查看/新增/修改客戶

ROLE_FLORIST(花藝師)

  • PRODUCT_R - 查看商品(設計時參考)
  • ORDER_R, ORDER_X - 查看訂單、變更設計相關狀態(開始設計、完成設計)
  • CUSTOMER_R - 查看客戶(了解需求)
  • 注意:沒有 ORDER_W,不能修改訂單內容

ROLE_DELIVERY(配送員)

  • ORDER_R, ORDER_X - 查看訂單、變更配送相關狀態(開始配送、確認簽收)
  • 注意:沒有 PRODUCT_*CUSTOMER_*

ROLE_ACCOUNTANT(會計)

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

ROLE_PURCHASER(採購)

  • PRODUCT_R, PRODUCT_W, PRODUCT_X - 完整的商品管理權限
  • ORDER_R, CUSTOMER_R - 查看訂單和客戶(了解需求)

6. 實作指南

6.1 Authority 常數定義

位置: src/main/java/io/leandev/app/security/Authority.java

public final class Authority {

// Product
public static final String PRODUCT_R = "PRODUCT_R";
public static final String PRODUCT_W = "PRODUCT_W";
public static final String PRODUCT_X = "PRODUCT_X";
public static final String PRODUCT_D = "PRODUCT_D";

// Order
public static final String ORDER_R = "ORDER_R";
public static final String ORDER_W = "ORDER_W";
public static final String ORDER_X = "ORDER_X";
public static final String ORDER_D = "ORDER_D";

// Customer
public static final String CUSTOMER_R = "CUSTOMER_R";
public static final String CUSTOMER_W = "CUSTOMER_W";
public static final String CUSTOMER_X = "CUSTOMER_X";
public static final String CUSTOMER_D = "CUSTOMER_D";

private Authority() {}
}

6.2 Role → Authority 映射配置

位置: src/main/java/io/leandev/app/security/RoleAuthorityMapper.java

@Component
public class RoleAuthorityMapper {

private static final Map<String, Set<String>> ROLE_AUTHORITIES = Map.ofEntries(
Map.entry("ROLE_ADMIN", Set.of(
PRODUCT_R, PRODUCT_W, PRODUCT_X, PRODUCT_D,
ORDER_R, ORDER_W, ORDER_X, ORDER_D,
CUSTOMER_R, CUSTOMER_W, CUSTOMER_X, CUSTOMER_D
)),
Map.entry("ROLE_SALES", Set.of(
PRODUCT_R,
ORDER_R, ORDER_W, ORDER_X,
CUSTOMER_R, CUSTOMER_W
)),
Map.entry("ROLE_FLORIST", Set.of(
PRODUCT_R,
ORDER_R, ORDER_X,
CUSTOMER_R
)),
Map.entry("ROLE_DELIVERY", Set.of(
ORDER_R, ORDER_X
))
// ... 其他角色
);

public Set<String> getAuthorities(String role) {
return ROLE_AUTHORITIES.getOrDefault(role, Set.of());
}
}

6.3 SecurityConfig 配置

@Configuration
@EnableMethodSecurity // 啟用 @PreAuthorize
public class SecurityConfig {
// ...
}

6.4 Controller 使用

@RestController
@RequestMapping("/api/v1/products")
public class ProductController {

@GetMapping
@PreAuthorize("hasAuthority('PRODUCT_R')")
public ResponseEntity<List<Product>> list() { ... }

@PostMapping
@PreAuthorize("hasAuthority('PRODUCT_W')")
public ResponseEntity<Product> create(@RequestBody Product product) { ... }

@PatchMapping("/{id}/status")
@PreAuthorize("hasAuthority('PRODUCT_X')")
public ResponseEntity<Product> updateStatus(@PathVariable String id) { ... }

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

7. 使用範例

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

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

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

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

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

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

// 確認簽收 - DELIVERY 可執行
@PatchMapping("/{id}/confirm-delivery")
@PreAuthorize("hasAuthority('ORDER_X')")
public ResponseEntity<Order> confirmDelivery(@PathVariable String id) { ... }

// 修改訂單內容 - 只有 SALES 以上可執行
@PatchMapping("/{id}")
@PreAuthorize("hasAuthority('ORDER_W')")
public ResponseEntity<Order> update(@PathVariable String id, @RequestBody Order order) { ... }
}

7.2 前端收到 403 的處理

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

附錄:設計決策記錄

日期決策原因
2025-12-23採用 R/W/X/D Authority 模型細粒度權限控制、類似 Linux 概念易理解
2025-12-23X 用於狀態變更,W 用於資料修改區分「執行操作」和「修改內容」的權限層級
2025-12-23D 獨立於 W刪除是較危險的操作,需要獨立控制

文檔維護者: Development Team + AI Assistant 最後審閱: 2025-12-23