在企業級應用開發中,用戶訪問控制始終是核心課題。傳統上,權限控制分為粗粒度與細粒度兩個層面:前者針對整個對象或對象群(如將文件設為唯讀),後者深入到方法或屬性級別(如允許修改文件的特定行)。優秀的權限系統需同時支援兩者,但傳統靜態 Proxy 模式為實現細粒度控制,往往需為每個業務類編寫對應的 Proxy 類(如 ForumProxy、ForumThreadProxy),導致類數量爆炸,系統複雜度大幅上升。
自 Java 1.3 起引入的 java.lang.reflect.Proxy 與 InvocationHandler,配合 java.security.acl 套件,提供了優雅的解決方案。動態 Proxy 可在運行時生成代理實例,攔截方法調用;ACL 則負責權限模型的構建與校驗。兩者結合,即可在單一代理類中實現多粒度的權限控制,大幅減少樣板代碼。
首先定義系統擁有者 Principal:
private static final Principal SECURITY_OWNER = new PrincipalImpl("SecurityOwner");
用戶登入後,從數據庫加載其對特定應用的權限集合(封裝為 Feature 對象,繼承自 Permission,細分讀/寫/刪等操作):
Hashtable features = loadFeaturesForUser(appName, userId);
創建用戶對象並注入權限:
User user = new UserImpl(userId, new Hashtable());
AclEntry entry = new AclEntryImpl(user);
// 遍歷 features,逐個加入權限
for (Object key : features.keySet()) {
Feature f = (Feature) features.get(key);
entry.addPermission(f);
}
// 最後加入系統擁有者,完成 ACL 初始化
acl.addAclEntry(SECURITY_OWNER, entry);
業務邏輯中只需調用:
if (acl.checkPermission(user, new Feature("read"))) { /* 放行 */ }
複雜的權限判斷邏輯完全委託給標準 ACL 實現,業務代碼保持乾淨。
假設系統有三種角色:訪客(唯讀)、員工(讀寫)、經理(讀寫)。傳統做法需分別定義 IReportReadOnly、IReportReadWrite 等介面,類爆炸顯而易見。動態 Proxy 方案如下:
public class ReportProxy implements InvocationHandler {
private final Map<String, Object> dataMap;
private final Acl acl;
private final Principal user;
public static Object newInstance(Map<String, Object> map, Class[] interfaces, Acl acl, Principal user) {
return Proxy.newProxyInstance(
map.getClass().getClassLoader(),
interfaces,
new ReportProxy(map, acl, user)
);
}
private ReportProxy(Map<String, Object> map, Acl acl, Principal user) {
this.dataMap = map; this.acl = acl; this.user = user;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName();
// getXXX / isXXX 視為讀操作
if (name.startsWith("get") || name.startsWith("is")) {
if (!acl.checkPermission(user, new Feature("read")))
throw new SecurityException("無讀取權限");
String prop = name.substring(name.indexOf("get") + 3); // 或 is + 2
return dataMap.get(prop);
}
// setXXX 視為寫操作
if (name.startsWith("set")) {
if (!acl.checkPermission(user, new Feature("write")))
throw new SecurityException("無寫入權限");
String prop = name.substring(name.indexOf("set") + 3);
dataMap.put(prop, args[0]);
return null;
}
return null; // 其他方法不處理
}
}
// 假設已有 acl、user、dataMap
Class[] ifaces = { IReport.class }; // 統一介面,含 get/set 方法
IReport report = (IReport) ReportProxy.newInstance(dataMap, ifaces, acl, user);
// 訪客調用
report.getTitle(); // 通過
report.setTitle("新標題"); // 拋出 SecurityException
動態 Proxy 與 Java ACL 的結合,以極少的代碼量實現了方法級的細粒度權限控制,同時保留了粗粒度的靈活性。這種模式適用於任何需要聲明式安全攔截的場景,是構建可維護、可擴展權限系統的經典範式。