角色基礎存取控制 (RBAC) 是企業級資訊系統中最主流的權限模型。結合 Struts 的 MVC 架構,本文提出一種將權限驗證邏輯集中在基底 Action 類別、透過 struts-config.xml 參數映射 Action 與權限關係的實作方案,有效解決傳統做法分散、維護困難、資料庫查詢頻繁等問題。
RBAC 引入「角色」作為中介層:
授權流程:資源權限 → 角色 → 用戶,實現用戶與具體權限的邏輯解耦。
Struts 應用中,功能模組對應 *.do 請求,由 ActionServlet 根據 struts-config.xml 分派至對應 Action。權限驗證的本質是:當前用戶是否擁有訪問該 Action (及其具體操作) 的權限?
| 方案 | 優點 | 缺點 |
|---|---|---|
| 各 Action 內硬編碼驗證 | 簡單直觀 | 邏輯分散、重複代碼多、修改困難、移植性差 |
| URL-權限映射存資料庫 | 靈活、好維護 | 每次請求查庫、效能差;同一 Action 多操作 (增/刪/改) 需細粒度 URL 設計 |
不將 URL 存資料庫,僅存實體關係,驗證時由記憶體中用戶物件直接判斷:
-- 使用者表
OA_USER { id, sys_name, password, ... }
-- 角色表
OA_ROLE { id, role_name, ... }
-- 權限表 (對應功能點代碼,如 ProjectDelete)
OA_POWER { id, power_name, ... }
-- 使用者-角色 關聯表
OA_USER_ROLE { id, user_id, role_id }
-- 角色-權限 關聯表
OA_ROLE_POWER { id, role_id, power_id }
解決單一 Action 處理多操作 (CRUD) 的粒度問題:
// 傳統: /project.do (無法區分新增/刪除)
/project.do?actionType=ProjectAdd
/project.do?actionType=ProjectDelete
/project.do?actionType=ProjectUpdate
actionType 值直接對應 OA_POWER.power_name。
利用 ActionMapping 的 parameter 屬性,以 actionType(Power_Name) 格式定義映射,多組用分號分隔:
<action path="/project"
type="com.example.ProjectAction"
name="projectForm"
scope="request"
parameter="ProjectAdd(ProjectAdd);ProjectDelete(ProjectDelete);ProjectUpdate(ProjectUpdate)"
validate="false">
<forward name="success" path="/projectList.do"/>
<forward name="nopower" path="/noPower.jsp"/>
</action>
優點:映射隨 Action 定義集中管理,無需額外資料表,啟動時載入記憶體,零資料庫查詢開銷。
設計抽象基類 BaseAction,覆寫 execute() 統一驗證,子類實作 doExecute():
public abstract class BaseAction extends Action {
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) {
if (checkPower(request, mapping)) {
return doExecute(mapping, form, request, response);
} else {
return mapping.findForward("nopower");
}
}
// 子類必須實作具體業務邏輯
public abstract ActionForward doExecute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response);
// 從 mapping.parameter 解析 actionType 對應的權限名稱
private String getActionPower(ActionMapping mapping) {
String actionType = request.getParameter("actionType");
String param = mapping.getParameter(); // 格式: "Add(ADD);Del(DEL)"
// 解析邏輯:找到 actionType 對應的 Power_Name
// 例如 actionType=ProjectDelete -> 返回 "ProjectDelete"
return parsePowerFromParameter(param, actionType);
}
// 核心驗證:登入用戶權限集合是否包含 needPower
private boolean checkPower(HttpServletRequest request, ActionMapping mapping) {
LoginUser loginUser = (LoginUser) request.getSession().getAttribute("currentUser");
if (loginUser == null) return false; // 未登入
String needPower = getActionPower(mapping);
if (needPower == null) return true; // 無需權限控制的操作
return loginUser.hasPower(needPower); // 用戶物件含權限集合,記憶體中 O(1) 判斷
}
}
用戶登入成功時,一次性查詢資料庫組裝權限集合存入 Session:
public class LoginUser {
private String userId;
private Set powers = new HashSet<>(); // 權限名稱集合
public boolean hasPower(String powerName) {
return powers.contains(powerName);
}
// 登入時由 Service 填充
public void loadPowers(Connection conn) {
// SQL: SELECT p.power_name FROM oa_power p
// JOIN oa_role_power rp ON p.id = rp.power_id
// JOIN oa_user_role ur ON rp.role_id = ur.role_id
// WHERE ur.user_id = ?
// 結果填入 this.powers
}
}
actionType 解決單 Action 多操作權限區分。struts-config.xml 直觀可見,修改即時生效 (重新載入配置或熱部署)。LoginUser.hasPower() 可輕鬆擴展支援通配符、正則、SpEL 表達式等高級匹配。雖然 Struts 1 已退役,但本方案的核心思想——「配置映射 + 基類攔截 + 會話快取權限」——在 Spring MVC 的 HandlerInterceptor、Shiro/Spring Security 的註解/配置式權限控制中均有體現。理解此經典實作,有助於深入掌握現代權限框架的底層原理。