傳統存取控制分為粗粒度(物件/目錄層級)與細粒度(方法/屬性層級)。早期使用靜態 Proxy 模式實現細粒度控制,需為每個業務類別建立對應 Proxy 類別(如 ForumProxy、ForumThreadProxy),導致類別爆炸、維護困難。Java 1.3 引入的動態 Proxy API 配合 java.security.acl 套件,提供了優雅的解決方案。
Principal securityOwner = new PrincipalImpl("SecurityOwner")User user = new UserImpl(userId, new Hashtable())Permission 物件,加入 AclEntry,最後綁定擁有者// 核心 ACL 建立邏輯
AclEntry newAclEntry = new AclEntryImpl(user);
Enumeration keys = features.keys();
while (keys.hasMoreElements()) {
String keyName = (String) keys.nextElement();
Feature feature = (Feature) features.get(keyName); // Feature 繼承 Permission
newAclEntry.addPermission(feature);
}
newAclEntry.setPrincipal(securityOwner); // 設定擁有者
acl.addEntry(securityOwner, newAclEntry);
定義統一業務介面,透過 InvocationHandler 統一攔截所有方法呼叫,根據方法名稱前綴(get/set/is)判斷操作類型,委派 ACL 檢查權限。
public class ReportProxy implements InvocationHandler {
private Map dataMap;
private Acl acl;
private Principal user;
public static Object newInstance(Map dataMap, Class[] interfaces, Acl acl, Principal user) {
return Proxy.newProxyInstance(
dataMap.getClass().getClassLoader(),
interfaces,
new ReportProxy(dataMap, acl, user)
);
}
private ReportProxy(Map dataMap, Acl acl, Principal user) {
this.dataMap = dataMap;
this.acl = acl;
this.user = user;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
// 讀取操作:get*/is*
if (methodName.startsWith("get") || methodName.startsWith("is")) {
if (!acl.checkPermission(user, new Feature("read"))) {
throw new SecurityException("無讀取權限");
}
String propName = methodName.substring(methodName.indexOf("get") + 3);
if (methodName.startsWith("is")) propName = methodName.substring(2);
return dataMap.get(propName);
}
// 寫入操作:set*
if (methodName.startsWith("set")) {
if (!acl.checkPermission(user, new Feature("write"))) {
throw new SecurityException("無寫入權限");
}
String propName = methodName.substring(3);
dataMap.put(propName, args[0]);
return null;
}
return null; // 其他方法預設拒絕
}
}
// 定義業務介面
public interface IReport {
String getName();
void setName(String name);
String getContent();
void setContent(String content);
boolean isPublished();
void setPublished(boolean published);
}
// 建立 Proxy 實例
Map reportData = new HashMap<>();
reportData.put("Name", "季度報表");
reportData.put("Content", "業績數據...");
reportData.put("Published", false);
IReport report = (IReport) ReportProxy.newInstance(
reportData,
new Class[]{IReport.class},
systemAcl,
currentUser
);
// 一般用戶:僅能讀取
System.out.println(report.getName()); // 允許
report.setName("新名稱"); // 拋出 SecurityException
// 管理者:可讀寫(ACL 中具備 write 權限)
report.setContent("更新內容"); // 允許
| 維度 | 靜態 Proxy | 動態 Proxy + ACL |
|---|---|---|
| 類別數量 | 每業務類一個 Proxy 類 | 單一通用 Proxy 處理所有介面 |
| 粒度控制 | 類別層級 | 方法/屬性層級 |
| 權限管理 | 硬編碼或分散配置 | 集中式 ACL,支援動態授權 |
| 擴充性 | 新增業務需新增 Proxy | 新增介面即可自動支援 |
checkPermission 結果減少 ACL 查詢開銷invoke 中記錄存取嘗試(成功/失敗),滿足合規需求動態 Proxy 與 ACL 的結合,以極小的程式碼量實現了企業級的細粒度存取控制,是 Java 安全架構設計的經典範式。