在 Struts+Spring+Hibernate 架構中,Web 層(Action/Controller)輕易通過 Session 獲取登錄用戶 ID,但業務邏輯層(Service/DAO)如何在不污染方法簽名(傳遞 userId 參數)的前提下獲得當前用戶身份,以實現方法級權限控制與審計日誌?Acegi Security(現 Spring Security 前身)提供了基於 ThreadLocal 的 SecurityContextHolder 機制,完美解決此問題。
下載 acegi-security-1.0.0.zip,將 JAR 放入 WEB-INF/lib。
參考「一餐三碗」示例 Acegi.zip 中的 SQL 建表(userinfo、authorities、user_auth 等)。
<filter>
<filter-name>Acegi Filter Chain Proxy</filter-name>
<filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>
<init-param>
<param-name>targetClass</param-name>
<param-value>org.acegisecurity.util.FilterChainProxy</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>Acegi Filter Chain Proxy</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.acegisecurity.ui.session.HttpSessionEventPublisher</listener-class>
</listener>
httpSessionContextIntegrationFilter(核心,將 SecurityContext 綁定到 ThreadLocal)、authenticationProcessingFilter(表單登錄)、exceptionTranslationFilter、filterInvocationInterceptor(URL 權限)。DaoAuthenticationProvider + JdbcDaoImpl 讀取數據庫,支援 MD5 密碼編碼(Md5PasswordEncoder)。MethodSecurityInterceptor + DataSourceMethodDefinitionSourceEditor 從數據庫動態加載方法-權限映射。<!-- aoptest.xml -->
<bean id="interceptTarget" class="com.mysoft.pmi.testcase.aop.TestAOPInterfaceImpl" singleton="false"/>
<bean id="welcomeAdvice" class="com.mysoft.pmi.testcase.aop.WelcomeAdvice"/>
<bean id="aopProxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces"><value>com.mysoft.pmi.testcase.aop.TestAOPInterface</value></property>
<property name="interceptorNames"><list><value>welcomeAdvice</value></list></property>
<property name="target"><ref bean="interceptTarget"/></property>
</bean>
public class WelcomeAdvice implements MethodBeforeAdvice {
public void before(Method method, Object[] args, Object target) throws Throwable {
SecurityContext ctx = SecurityContextHolder.getContext();
if (ctx != null) {
Authentication auth = ctx.getAuthentication();
if (auth != null) {
Object principal = auth.getPrincipal();
if (principal instanceof UserDetails) {
String username = ((UserDetails) principal).getUsername();
String password = ((UserDetails) principal).getPassword();
System.out.println("當前用戶: " + username);
// 此處可進行細粒度權限檢查、記錄審計日誌等
} else {
System.out.println("匿名/非標準主體: " + principal);
}
}
}
// 繼續輸出調試信息...
}
}
HttpSessionContextIntegrationFilter 在請求開始時從 Session 讀取 SecurityContext 綁定到 SecurityContextHolder(ThreadLocal),請求結束時清理。業務層線程相同,故可直接獲取。MethodSecurityInterceptor 實現聲明式方法權限;Advice 中可接入規則引擎實現數據級授權(如:同樣是「入庫單錄入」,原料庫管員只能錄原料,成品庫管員只能錄成品)。acegilogin.jsp,輸入 root/root(數據庫預設)。secure/index.jsp,點擊 Home 鏈接調用 testacegi.jsp。testacegi.jsp 通過 Spring 容器獲取代理 Bean,調用 test()/testAAA()。WelcomeAdvice 中打印的用戶名,驗證成功。SecurityContextHolder 獲取邏輯封裝為獨立工具類(如 SecurityContextUtils.getCurrentUser()),業務代碼調用工具類而非直接依賴 Acegi API,便於未來遷移至 Spring Security。Md5PasswordEncoder 或更強算法(BCrypt),配置於 DaoAuthenticationProvider。authorities 中 PROTECTED_RES 填寫完整接口方法名(如 com.mysoft.pmi.testcase.aop.TestAOPInterface.test),否則啟動時報錯。Acegi 通過 ThreadLocal 機制實現了 Web 身份向業務層的無縫傳遞,配合 Spring AOP 實現方法級安全與審計,是分層架構中解決「身份傳遞」的經典方案。代碼結構清晰、擴展性強,值得在項目中推廣應用。
作者:王保政
MSN: baozhengw999@hotmail.com
Email: baozhengw@netease.com
QQ: 29803446