在 Struts 2 架構下,權限控制必須覆蓋兩個維度:
利用 Struts 2 攔截器鏈機制,在 Action 執行前統一驗證。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresPermission {
String value(); // 權限標識,如 "user:create"
}
public class PermissionInterceptor extends AbstractInterceptor {
private static final Logger LOG = LoggerFactory.getLogger(PermissionInterceptor.class);
@Override
public String intercept(ActionInvocation invocation) throws Exception {
ActionProxy proxy = invocation.getProxy();
String namespace = proxy.getNamespace();
String actionName = proxy.getActionName();
String methodName = proxy.getMethod();
// 1. 獲取當前登入用戶 (假設存放在 Session)
UserPrincipal user = (UserPrincipal) ActionContext.getContext().getSession().get("USER_PRINCIPAL");
if (user == null) {
LOG.warn("未登入用戶嘗試訪問: {}/{}", namespace, actionName);
return "login"; // 全域結果類型對應登入頁
}
// 2. 讀取方法級註解
Method method = invocation.getAction().getClass().getMethod(methodName);
RequiresPermission rp = method.getAnnotation(RequiresPermission.class);
if (rp != null) {
String requiredPerm = rp.value();
if (!user.hasPermission(requiredPerm)) {
LOG.warn("用戶 {} 缺少權限 {} 訪問 {}.{}", user.getUsername(), requiredPerm, actionName, methodName);
return "unauthorized"; // 對應 403 頁面
}
}
return invocation.invoke();
}
}
<package name="secure" extends="struts-default">
<interceptors>
<interceptor name="permCheck" class="com.example.interceptor.PermissionInterceptor"/>
<interceptor-stack name="secureStack">
<interceptor-ref name="defaultStack"/>
<interceptor-ref name="permCheck"/>
</interceptor-stack>
</interceptors>
<default-interceptor-ref name="secureStack"/>
<global-results>
<result name="login">/login.jsp</result>
<result name="unauthorized">/403.jsp</result>
</global-results>
</package>
@Namespace("/admin")
@Action(value = "user", results = { @Result(name="success", location="/user/list.jsp") })
public class UserAction extends ActionSupport {
@RequiresPermission("user:create")
public String create() { ... }
@RequiresPermission("user:delete")
public String delete() { ... }
}
自定義標籤 <sec:hasPermission>,配合 CSS/JS 隱藏元素:
<%@ taglib prefix="sec" uri="/security-tags" %>
<sec:hasPermission permission="user:create">
<button id="btnAddUser">新增用戶</button>
</sec:hasPermission>
<sec:lacksPermission permission="user:delete">
<script>$('#btnDeleteUser').hide();</script>
</sec:lacksPermission>
標籤實現核心邏輯:從 Session 取得 UserPrincipal,調用 hasPermission(String) 判斷,決定是否輸出 BodyContent。
若專案已引入 Spring,強烈建議直接使用 Spring Security:
@PreAuthorize("hasAuthority('user:create')") 方法級安全。Struts 2 整合方式:web.xml 配置 DelegatingFilterProxy,Action 由 Spring 管理,註解即時生效。
| 層級 | 技術 | 防禦目標 |
|---|---|---|
| Servlet Filter | Spring Security / 自定義 Filter | 靜態資源、早期惡意請求攔截 |
| Struts Interceptor | PermissionInterceptor + 註解 | Action/方法級業務權限控制 |
| View (JSP/FTL) | 自定義標籤 / Thymeleaf Sec | UI 元素動態顯隱,體驗優化 |
| Service/DAO | 程式化檢查 / AOP | 核心資料操作最後一道防線 |
權限系統設計原則:預設拒絕、最小權限、集中配置、審計日誌。切勿將權限邏輯硬編碼在 Action 內部,應透過攔截器/AOP 實現橫切關注點分離。