首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

基於 AOP 與 RBAC 的動態表單權限模型設計與 AspectJ 實現

     編輯:Java 相關知識

背景:傳統 RBAC 在 Web 系統的擴展困境

傳統 RBAC(Role-Based Access Control)以「用戶-角色-權限」三元組為核心,但在 B/S 架構下面臨挑戰:

  • 權限顆粒度難定:頁面層級太粗,按鈕/欄位層級太細,維護成本高。
  • 代碼侵入性強:Controller/Service 充斥 if (hasPerm(...)),違反開閉原則。
  • 動態授權不靈活:權限變更需重啟或清理緩存,無法即時生效。

本文提出「角色-表單」權限模型,結合 AspectJ 編譯期織入,實現零侵入、細顆粒度、熱生效的權限控制。

一、 角色-表單權限模型設計

1. 核心實體關係圖

User (用戶)  ←N:M→  Role (角色)  ←N:M→  Permission (權限/表單功能點)
                            ↑
                      Session (會話上下文,持有當前激活角色集)

2. 資料庫 schema 精簡版

表名關鍵欄位說明
sys_userid, username, password_hash, status用戶主檔
sys_roleid, code, name, description角色定義
sys_permissionid, form_code, action_code, description表單功能點,如 "ORDER_FORM:SUBMIT"
sys_role_permrole_id, perm_id角色-權限關聯
sys_user_roleuser_id, role_id用戶-角色關聯

關鍵設計:Permission 綁定 form_code + action_code,對應前端具體表單提交/按鈕操作,而非模糊的菜單 URL。

二、 AspectJ 實現:編譯期織入實現零侵入

1. 為何選 AspectJ 而非 Spring AOP?

特性Spring AOP (代理)AspectJ (編譯期/類載入期)
攔截範圍僅 public 方法、外部調用任意方法、構造函數、欄位存取、異常拋出
性能代理鏈開銷零運行時開銷,直接內聯字節碼
依賴Spring 容器管理 Bean無容器依賴,POJO 即可織入
熱部署支援需 Load-Time Weaving (LTW) 或重新編譯

權限屬橫切關注點,需攔截 Controller 內部私有方法、Service 異常流等,AspectJ 更適合。

2. 核心切面定義

@Aspect
public class FormPermissionAspect {

    @Autowired private PermissionService permService; // 注入權限查詢服務

    /**
     * 切點:所有標註 @RequiresFormPerm 的方法執行
     * 也可用 execution(* com.example.web..*Controller.*(..)) 捕獲整層
     */
    @Pointcut("@annotation(com.example.annotation.RequiresFormPerm)")
    public void formPermPointcut() {}

    /**
     * 環繞通知:權限校驗 + 原方法執行
     */
    @Around("formPermPointcut()")
    public Object checkPermission(ProceedingJoinPoint pjp) throws Throwable {
        MethodSignature sig = (MethodSignature) pjp.getSignature();
        RequiresFormPerm ann = sig.getMethod().getAnnotation(RequiresFormPerm.class);
        
        String formCode = ann.formCode();
        String actionCode = ann.actionCode();
        
        // 從 ThreadLocal/Session 獲取當前用戶上下文
        UserContext ctx = UserContextHolder.get();
        if (ctx == null || !permService.hasFormPermission(ctx.getUserId(), formCode, actionCode)) {
            throw new AccessDeniedException("無權限操作表單 [" + formCode + "] 動作 [" + actionCode + "]");
        }
        
        return pjp.proceed(); // 通過校驗,執行原方法
    }
}

3. 註解定義與使用範例

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME) // AspectJ 需保留至運行期讀取註解屬性
public @interface RequiresFormPerm {
    String formCode();  // 表單代碼,如 "PURCHASE_ORDER"
    String actionCode(); // 動作代碼,如 "APPROVE", "REJECT", "SAVE_DRAFT"
}

// Controller 使用
@Controller
@RequestMapping("/purchase")
public class PurchaseOrderController {
    
    @RequiresFormPerm(formCode = "PURCHASE_ORDER", actionCode = "APPROVE")
    @PostMapping("/approve/{id}")
    public Result approve(@PathVariable Long id) { ... }
    
    @RequiresFormPerm(formCode = "PURCHASE_ORDER", actionCode = "SAVE_DRAFT")
    @PostMapping("/draft")
    public Result saveDraft(@RequestBody PurchaseOrderDTO dto) { ... }
}

4. 通配符切點批量攔截(減少樣板碼)

對於遵循命名慣例的專案,可用一個切面覆蓋整個包:

@Pointcut("execution(* com.example.web..*Controller.*(..))")
public void controllerLayer() {}

@Around("controllerLayer()")
public Object globalFormPermCheck(ProceedingJoinPoint pjp) throws Throwable {
    // 從方法名/類名推導 formCode/actionCode,或讀取方法級註解
    // 若無註解則放行(預設公開)或攔截(預設拒絕),視安全策略而定
    ...
}

三、 關鍵技術細節與坑點

1. 編譯期織入 (CTW) 配置

Maven 專案引入 aspectj-maven-plugin:

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    <version>1.14.0</version>
    <configuration>
        <complianceLevel>17</complianceLevel>
        <source>17</source>
        <target>17</target>
        <showWeaveInfo>true</showWeaveInfo>
        <verbose>true</verbose>
        <Xlint>ignore</Xlint>
    </configuration>
    <executions>
        <execution>
            <goals><goal>compile</goal></goals>
        </execution>
    </executions>
</plugin>

2. 權限查詢效能優化

  • 多級緩存:用戶權限集合 → Redis (集群共享) → 本地 Caffeine (熱點加速)。
  • 版本號機制:權限變更時遞增全局版本號,緩存 Key 含版本號,實現無感熱更新。
  • 批量預載:登入時一次性載入用戶所有表單權限至 Session/Token,避免每請求查 DB。

3. 異常統一處理

@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(AccessDeniedException.class)
    @ResponseStatus(HttpStatus.FORBIDDEN)
    public Result handleAccessDenied(AccessDeniedException e) {
        return Result.fail(403, e.getMessage());
    }
}

四、 方案優勢總結

維度傳統硬編碼本方案 (AspectJ + RBAC)
代碼侵入性高,業務邏輯混雜權限判斷零侵入,註解聲明式
顆粒度通常僅到 URL/Action表單功能點級 (按鈕/欄位/操作)
維護性修改權限需改代碼重啟資料庫配置即時生效(配合緩存版本號)
複用性難以跨模塊復用切面集中管理,自動應用於所有受管方法
測試性需 Mock 權限上下文單元測試可直接調用方法,整合測試驗證切面織入

架構師建議:中小專案優先用 Spring Security + @PreAuthorize,架構簡單、生態完善。大型系統、需極致效能、細顆粒度欄位級控制、或非 Spring 容器環境時,AspectJ 編譯期織入是更專業的選擇。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved