訪問控制是企業級應用的核心安全需求。傳統粗粒度控制僅能限制對象整體訪問(如文件只讀),而細粒度控制需深入方法、屬性級別(如允許修改文件特定行)。Jive 等早期框架採用靜態代理模式,導致代理類爆炸(ForumProxy、ForumThreadProxy...),系統複雜度劇增。動態代理 + Java ACL 的組合方案優雅解決了這一難題。
SecurityOwnercheckPermission() 判斷// 1. 建立主體擁有者
private static final Principal SECURITY_OWNER = new PrincipalImpl("SecurityOwner");
// 2. 用戶登錄驗證後,從數據庫加載權限
Hashtable features = loadFeaturesForUser(appName, userId); // 包含讀/寫/刪等 Feature
// 3. 創建用戶對象
User user = new UserImpl(userId, new Hashtable());
// 4. 為用戶創建活動 ACL Entry
addAclEntry(user, features);
// 關鍵實現:addAclEntry
void addAclEntry(User user, Hashtable features) {
AclEntry newAclEntry = new AclEntryImpl(user);
// 遍歷權限集合添加
for (Object key : features.keySet()) {
Feature feature = (Feature) features.get(key);
newAclEntry.addPermission(feature);
}
// 必須添加主體擁有者
newAclEntry.addPermission(new PermissionImpl(SECURITY_OWNER));
acl.addEntry(newAclEntry);
}
// 業務代碼中僅需一行調用
if (acl.checkPermission(user, feature)) {
// 允許操作
}
複雜的權限判定邏輯完全委託給 java.security.acl.ACL 實現。
invoke() 方法場景:三類用戶 —— 普通用戶(唯讀)、員工(讀寫)、經理(讀寫)
public class ReportProxy implements InvocationHandler {
private Map<String, Object> propertyMap; // 模擬對象屬性存儲
private Acl acl;
private User currentUser;
public static Object newInstance(Map<String, Object> map, Class[] interfaces, Acl acl, User user) {
ReportProxy handler = new ReportProxy(map, acl, user);
return Proxy.newProxyInstance(
map.getClass().getClassLoader(),
interfaces,
handler
);
}
private ReportProxy(Map<String, Object> map, Acl acl, User user) {
this.propertyMap = map;
this.acl = acl;
this.currentUser = user;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
// getter / isXXX → 讀權限
if (methodName.startsWith("get") || methodName.startsWith("is")) {
if (!acl.checkPermission(currentUser, "read")) {
throw new SecurityException("無讀權限");
}
String propName = methodName.substring(methodName.startsWith("get") ? 3 : 2);
// 首字母小寫轉換
propName = Character.toLowerCase(propName.charAt(0)) + propName.substring(1);
return propertyMap.get(propName);
}
// setter → 寫權限
if (methodName.startsWith("set")) {
if (!acl.checkPermission(currentUser, "write")) {
throw new SecurityException("無寫權限");
}
String propName = methodName.substring(3);
propName = Character.toLowerCase(propName.charAt(0)) + propName.substring(1);
propertyMap.put(propName, args[0]);
return null;
}
return null; // 其他方法默認拒絕
}
}
// 定義業務接口(無需分離讀寫接口)
public interface IReport {
String getTitle();
void setTitle(String title);
String getContent();
void setContent(String content);
boolean isPublished();
void setPublished(boolean published);
}
// 客戶端調用
IReport report = (IReport) ReportProxy.newInstance(propMap, new Class[]{IReport.class}, acl, currentUser);
System.out.println(report.getTitle()); // 自動檢查 read 權限
report.setTitle("新標題"); // 自動檢查 write 權限
| 傳統靜態代理 | 動態代理 + ACL |
|---|---|
| 每業務類需編寫專用代理類 | 單一通用代理處理所有接口 |
| 代理類數量隨業務增長線性增長 | 零代理類增長,僅需配置 ACL |
| 粗粒度控制為主,細粒度極難實現 | 天然支持方法/屬性級細粒度控制 |
| 權限邏輯分散在各代理類中 | 權限邏輯集中在 ACL + InvocationHandler |
SecurityException 或自定義權限異常,由全局異常處理器轉為友好提示動態代理與 Java ACL 的結合,以極小的代碼量實現了企業級細粒度訪問控制。這一方案不僅消除了代理類爆炸問題,更將權限邏輯集中化、標準化,是構建安全、可維護企業應用的經典架構模式。