Acegi安全系統介紹(一)介紹如何在業務接口層獲得Web層的用戶身份 王保政 Email:
[email protected] QQ:29803446 MSN:
[email protected] 2006-6-30 目 錄前 言 3 第一章 環境搭建 4 1.1 環境配置 4 第二章 相關的JAVA類 13 2.1 接口TESTAOPINTERFACE 13 2.2 接口實現類TESTAOPINTERFACEIMPL 14 2.3 接口實現類TESTAOPINTERFACE的ADVICE類 14 2.4 DATASOURCEMETHODDEFINITIONSOURCEEDITOR類 17 第三章 測試頁面 18 3.1 登錄頁面 18 3.2 登錄後進入INDEX.JSP 19 3.3 調用業務方法的頁面 20 前 言我在公司產品升級中引入struts+spring+hibernate3的框架時,那時還沒有引入Acegi安全系統框架,直到在最近的開發中發現了這樣一個問題:Web層可以很容易地得到登錄用戶的userId,但業務邏輯層的接口如何獲得UserId來進行方法調用的安全控制?一開始的接口設計中,有不少方法的第一個參數都是一個String類型的userId變量,但總不能每個方法都帶一個userId做為參數吧?那當然不能。那麼如何將userId傳給業務邏輯層的接口,或者傳給業務邏輯接口的前置通知類(MethodBeforeAdvice)?在Advice類獲得userId後,就可以根據userId控制方法的執行權限。既然不能使用業務方法中帶userId參數的笨拙辦法,那麼有什麼其他的方法可以傳遞userId呢?我最初考慮由前端傳遞一個Session,後來又考慮開發一個Listener,但我還沒來得及去嘗試這些方法的時候,偶爾翻了翻買來的《Spring in Action中文版》中介紹的acegi安全系統,覺得這個安全框架既然能夠實現方法級的權限控制,就一定可以解決我現在遇到的問題:我要在業務接口的Advice類中獲得登錄用戶的身份!《Spring in Action中文版》介紹的內容比較基礎,沒有直接給出一個現成的范例,於是到網上狂找一氣,在google上用acegi關鍵字搜索了10頁,找到幾篇好文章,最有幫助的應該是網名為“一餐三碗”的先生的一個示例,不過在看他的例子是用acegi0.8.3做的,而我那時手頭已經下載了acegi1.0,於是結合acegi1.0下載包和“一餐三碗”老兄的Demo,花了一整天的時間,終於調通了一個基於acegi1.0的例子,這個例子配置了MD5加密,後台的口令驗證是根據登錄表單的口令進行MD5加密後,與數據庫用戶表中此用戶的加密口令進行比較。我想大家一定很著急先知道我們如何實現在業務方法或Advice類中獲得用戶身份,所以我先介紹這個例子的具體實現,然後再介紹一下Acegi。 第一章 環境搭建 1.1 環境配置 1、下載acegi: http://nchc.dl.sourceforge.net/sourceforge/acegisecurity/acegi-security-1.0.0-src.zip http://prdownloads.sourceforge.net/acegisecurity/acegi-security-1.0.0.zip?download 下載後需要將zip文件中的jar包解壓出來,放到Web應用可以找到的lib目錄下。 2、配置MySQL庫 當然也可以使用其他數據庫,這裡以MySql庫為例,MySQL安裝好以後需要建幾個表,大家可以從這個地址下載“一餐三碗”先生(從名字看應該是男士)提供的例子,裡面有數據庫表的sql文件: http://www.blogJava.Net/Files/youlq/Acegi.zip。 3、配置 web.xml 〈?xml version="1.0" encoding="GB2312"?〉〈!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"〉 〈web-app〉 〈display-name〉acegi10〈/display-name〉 〈context-param〉 〈param-name〉contextConfigLocation〈/param-name〉 〈param-value〉/WEB-INF/classes/applicationContext-acegi-security.xml /WEB-INF/classes/applicationContext.xml 〈/param-value〉 〈/context-param〉 〈context-param〉 〈param-name〉log4jConfigLocation〈/param-name〉 〈param-value〉/WEB-INF/classes/log4j.propertIEs〈/param-value〉 〈/context-param〉 〈servlet〉 〈servlet-name〉SpringContextServlet〈/servlet-name〉 〈servlet-class〉 org.springframework.web.context.ContextLoaderServlet 〈/servlet-class〉 〈load-on-startup〉1〈/load-on-startup〉 〈/servlet〉 〈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〉 〈!-- - Loads the root application context of this web app at startup. - The application context is then available via - WebApplicationContextUtils.getWebApplicationContext(servletContext). --〉 〈listener〉 〈listener-class〉org.springframework.web.context.ContextLoaderListener〈/listener-class〉 〈/listener〉 〈listener〉 〈listener-class〉org.springframework.web.util.Log4jConfigListener〈/listener-class〉 〈/listener〉 〈!-- The HttpSessionEventPublisher will publish HttpSessionCreatedEvent and HttpSessionDestroyedEvent to the WebApplicationContext --〉 〈listener〉 〈listener-class〉org.acegisecurity.ui.session.HttpSessionEventPublisher〈/listener-class〉 〈/listener〉 〈/web-app〉 4、WEB-INF\classes下的applicationContext.xml文件: 〈?xml version="1.0" encoding="UTF-8"?〉〈!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"〉〈beans〉 〈bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"〉 〈property name="driverClass"〉 〈value〉com.mysql.jdbc.Driver〈/value〉 〈/property〉 〈property name="jdbcUrl"〉 〈value〉jdbc:mysql://localhost:3308/acegidb〈/value〉 〈/property〉 〈property name="user"〉 〈value〉root〈/value〉 〈/property〉 〈property name="passWord"〉 〈value〉MySQL〈/value〉 〈/property〉 〈property name="initialPoolSize"〉 〈value〉10〈/value〉 〈/property〉 〈property name="minPoolSize"〉 〈value〉10〈/value〉 〈/property〉 〈property name="maxPoolSize"〉 〈value〉50〈/value〉 〈/property〉 〈property name="checkoutTimeout"〉 〈value〉50000〈/value〉 〈/property〉 〈property name="maxIdleTime"〉 〈value〉1800〈/value〉 〈/property〉 〈property name="idleConnectionTestPeriod"〉 〈value〉3000〈/value〉 〈/property〉 〈property name="acquireIncrement"〉 〈value〉5〈/value〉 〈/property〉 〈/bean〉 〈bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"〉 〈property name="dataSource"〉 〈ref local="dataSource"/〉 〈/property〉 〈/bean〉 〈bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"〉 〈property name="dataSource"〉 〈ref bean="dataSource"/〉 〈/property〉 〈/bean〉 〈bean id="businessAccessDecisionManager" class="org.acegisecurity.vote.AffirmativeBased"〉 〈property name="allowIfAllAbstainDecisions"〉〈value〉false〈/value〉〈/property〉 〈property name="decisionVoters"〉 〈list〉 〈ref bean="roleVoter"/〉 〈/list〉 〈/property〉 〈/bean〉 〈bean id="customEditorConfigurer" class="org.springframework.beans.factory.config.CustomEditorConfigurer"〉 〈property name="customEditors"〉 〈map〉 〈entry key="org.acegisecurity.intercept.method.MethodDefinitionSource"〉 〈bean class="com.mysoft.pmi.security.DataSourceMethodDefinitionSourceEditor"〉 〈property name="jdbcTemplate"〉 〈ref bean="jdbcTemplate"/〉 〈/property〉 〈/bean〉 〈/entry〉 〈/map〉 〈/property〉 〈/bean〉 〈bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor"〉 〈property name="transactionManager"〉〈ref bean="transactionManager"/〉〈/property〉 〈property name="transactionAttributeSource"〉 〈value〉 com.mysoft.pmi.testcase.aop.TestAOPInterfaceImpl.*=PROPAGATION_REQUIRED 〈/value〉 〈/property〉 〈/bean〉 〈bean id="contactManagerTarget" class="com.mysoft.pmi.testcase.aop.TestAOPInterfaceImpl" /〉 〈bean id="contactManagerSecurity" class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor"〉 〈property name="authenticationManager"〉〈ref bean="authenticationManager"/〉〈/property〉 〈property name="accessDecisionManager"〉〈ref bean="businessAccessDecisionManager"/〉〈/property〉 〈property name="objectDefinitionSource"〉 〈value〉 select authority,PROTECTED_RES from authorities where AUTH_TYPE='FUNCTION' and authority like 'AUTH_FUNC_ContactManager%' 〈/value〉 〈/property〉 〈/bean〉 〈bean id="contactManager" class="org.springframework.aop.framework.ProxyFactoryBean"〉 〈property name="proxyInterfaces"〉 〈value〉com.mysoft.pmi.testcase.aop.TestAOPInterface〈/value〉 〈/property〉 〈property name="interceptorNames"〉 〈list〉 〈idref local="transactionInterceptor"/〉 〈idref local="contactManagerSecurity"/〉 〈idref local="contactManagerTarget"/〉 〈/list〉 〈/property〉 〈/bean〉 〈/beans〉 說明:請根據自己機器的配置更改數據庫連接地址、端口、數據庫名、口令配置參數。com.mysoft.pmi.testcase.aop.TestAOPInterface接口是一個業務邏輯層接口,本例com.mysoft.pmi.testcase.aop.TestAOPInterfaceImpl是此接口的實現類,提供了兩個非常簡單的接口方法:test和testAAA。 另外注意使用“一餐三碗”的數據庫導入表結構和數據後,需要修改authoritIEs表PROTECTED_RES的值,因為本例使用的接口為com.mysoft.pmi.testcase.aop.TestAOPInterface,如果此字段使用了不存在的接口和方法,在應用服務器啟動時會報錯,所以請將此字段的內容改為:com.mysoft.pmi.testcase.aop.TestAOPInterface.test或com.mysoft.pmi.testcase.aop.TestAOPInterface.testAAA,如果大家有興趣可以在此接口下多加幾個方法。 5、WEB-INF\classes下的applicationContext-acegi-security.xml文件: 〈?xml version="1.0" encoding="UTF-8"?〉〈!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"〉 〈beans〉 〈bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy"〉 〈property name="filterInvocationDefinitionSource"〉 〈value〉 CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON PATTERN_TYPE_apache_ANT /**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,securityContextHolderAwareRequestFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor 〈/value〉 〈/property〉 〈/bean〉 〈bean id="httpSessionContextIntegrationFilter" class="org.acegisecurity.context.HttpSessionContextIntegrationFilter"/〉 〈bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter"〉 〈property name="authenticationManager" ref="authenticationManager"/〉 〈property name="authenticationFailureUrl" value="/index_acegi.jsp?login_error=1"/〉 〈property name="defaultTargetUrl" value="/testacegi.jsp"/〉 〈property name="filterProcessesUrl" value="/j_acegi_security_check"/〉 〈/bean〉 〈bean id="securityContextHolderAwareRequestFilter" class="org.acegisecurity.wrapper.SecurityContextHolderAwareRequestFilter"/〉 〈bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter"〉 〈property name="authenticationEntryPoint"〉 〈bean class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint"〉 〈property name="loginFormUrl" value="/acegilogin.jsp"/〉 〈property name="forceHttps" value="false"/〉 〈/bean〉 〈/property〉 〈property name="accessDeniedHandler"〉 〈bean class="org.acegisecurity.ui.AccessDenIEdHandlerImpl"〉 〈property name="errorPage" value="/AccessDenIEd.JSp"/〉 〈/bean〉 〈/property〉 〈/bean〉 〈bean id="filterInvocationInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor"〉 〈property name="authenticationManager" ref="authenticationManager"/〉 〈property name="AccessDecisionManager"〉 〈bean class="org.acegisecurity.vote.AffirmativeBased"〉 〈property name="allowIfAllAbstainDecisions" value="false"/〉 〈property name="decisionVoters"〉 〈list〉 〈ref bean="roleVoter"/〉 〈bean class="org.acegisecurity.vote.AuthenticatedVoter"/〉 〈/list〉 〈/property〉 〈/bean〉 〈/property〉 〈property name="objectDefinitionSource"〉 〈value〉 CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON PATTERN_TYPE_APACHE_ANT /secure/extreme/**=AUTH_USER /secure/**=IS_AUTHENTICATED_REMEMBERED /**=IS_AUTHENTICATED_ANONYMOUSLY 〈/value〉 〈/property〉 〈/bean〉 〈bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager"〉 〈property name="providers"〉 〈list〉 〈ref local="daoAuthenticationProvider"/〉 〈ref local="anonymousAuthenticationProvider"/〉 〈/list〉 〈/property〉 〈/bean〉 〈bean id="anonymousProcessingFilter" class="org.acegisecurity.providers.anonymous.AnonymousProcessingFilter"〉 〈property name="key"〉〈value〉foobar〈/value〉〈/property〉 〈property name="userAttribute"〉〈value〉anonymousUser,AUTH_ANONYMOUS〈/value〉〈/property〉 〈/bean〉 〈bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider"〉 〈property name="userDetailsService" ref="jdbcDaoImpl"/〉 〈property name="userCache"〉〈ref local="userCache"/〉〈/property〉 〈!-- if u want encode your password with md5,use property passwordEncoder,otherwise delete row below--〉 〈!--〈property name="passwordEncoder" ref="passwordEncoder"/〉--〉 〈/bean〉 〈bean id="passwordEncoder" class="org.acegisecurity.providers.encoding.Md5PasswordEncoder"/〉 〈bean id="loggerListener" class="org.acegisecurity.event.authentication.LoggerListener"/〉 〈!-- add --〉 〈bean id="jdbcDaoImpl" class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl"〉 〈property name="dataSource"〉〈ref bean= "dataSource"/〉〈/property〉 〈property name="usersByUsernameQuery"〉 〈!-- 〈value〉SELECT USERID, USERPASSWORD,IS_ENABLED FROM LTPMIUSER WHERE USERID=?〈/value〉 --〉 〈value〉SELECT USERNAME, PASSWORD,ENABLED FROM USERINFO WHERE USERNAME=?〈/value〉 〈/property〉 〈property name="authoritiesByUsernameQuery"〉 〈!-- 〈value〉 SELECT userid,authority FROM ltpmiuser u, authorities a, user_auth ua WHERE u.object_id=ua.user_oid and a.auth_id=ua.auth_id and u.userid = ? 〈/value〉 --〉 〈value〉 SELECT username,authority FROM userinfo u, authorities a, user_auth ua WHERE u.user_id=ua.user_id and a.auth_id=ua.auth_id and u.username = ? 〈/value〉 〈/property〉 〈/bean〉 〈bean id="roleVoter" class="org.acegisecurity.vote.RoleVoter"〉 〈property name="rolePrefix"〉〈value〉AUTH_〈/value〉〈/property〉 〈/bean〉 〈bean id="anonymousAuthenticationProvider" class="org.acegisecurity.providers.anonymous.AnonymousAuthenticationProvider"〉 〈property name="key"〉〈value〉foobar〈/value〉〈/property〉 〈/bean〉 〈bean id="userCacheBackend" class="org.springframework.cache.ehcache.EhCacheFactoryBean"〉 〈property name="cacheManager"〉 〈ref local="cacheManager"/〉 〈/property〉 〈property name="cacheName"〉 〈value〉userCache〈/value〉 〈/property〉 〈/bean〉 〈bean id="userCache" class="org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache"〉 〈property name="cache"〉〈ref local="userCacheBackend"/〉〈/property〉 〈/bean〉 〈bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/〉 〈/beans〉 說明:如果你想使用md5口令驗證,請去掉〈!--〈property name="passwordEncoder" ref="passwordEncoder"/〉--〉的注釋。 6、log4j.propertIEs 放在WEB-INF\classes下: log4j.rootLogger=info,CONSOLE log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.CONSOLE.Target=System.out log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout log4j.appender.CONSOLE.layout.ConversionPattern= %4p [%t] (%F:%L) - %m%n 7、配置業務方法的前置通知(Advice) 在業務方法的前置通知類編寫獲取Web層用戶身份的代碼,在業務方法的後置通知類編寫操作日志的方法調用,在操作日志方法中可通過acegi提供的API來獲取當前操作者的Authenticaton。實際上acegi提供了一個非常松散的集成機制,如果你僅需要使用acegi獲得Web層的身份,只需要在Advice類中引入acegi的方法,即使我們以後不使用acegi,也使代碼的修改非常有限。業務接口:TestAOPInterface。下面是為TestAOPInterface的Advice配置的xml文件aoptest.xml。 文件的類路徑為:com.mysoft.pmi.resource。文件內容:〈?xml version="1.0" encoding="UTF-8"?〉〈!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"〉 〈beans〉〈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"〉 〈list〉 〈value〉com.mysoft.pmi.testcase.aop.TestAOPInterface〈/value〉 〈/list〉 〈/property〉 〈property name="interceptorNames"〉 〈list〉 〈value〉welcomeAdvice〈/value〉 〈/list〉 〈/property〉 〈property name="target"〉〈ref bean="interceptTarget"/〉 〈/property〉 〈/bean〉〈/beans〉 第二章 相關的Java類 2.1 接口TestAOPInterface 此接口的角色為業務邏輯層接口。 package com.mysoft.pmi.testcase.aop; public interface TestAOPInterface { public void test(String arg0,String arg1); public void testAAA(String arg0,String arg1); } 2.2 接口實現類TestAOPInterfaceImpl package com.mysoft.pmi.testcase.aop; public class TestAOPInterfaceImpl implements TestAOPInterface { public void test(String arg0, String arg1) { System.out.println(arg0+"/"+arg1); } public void testAAA(String arg0, String arg1) { System.out.println(arg0+":"+arg1); } } 2.3 接口實現類TestAOPInterface的Advice類 package com.mysoft.pmi.testcase.aop; import java.lang.reflect.Method; import org.springframework.aop.MethodBeforeAdvice; import org.acegisecurity.context.SecurityContext; import org.acegisecurity.context.SecurityContextHolder; import org.acegisecurity.userdetails.UserDetails; import org.acegisecurity.Authentication; //import org.acegisecurity.vote.RoleVoter; //import org.apache.log4j.Logger; //import org.acegisecurity.providers.ProviderManager; //import org.acegisecurity.vote.AffirmativeBased; //import org.acegisecurity.ui.session.HttpSessionEventPublisher; //import org.acegisecurity.providers.anonymous.AnonymousProcessingFilter; //import org.acegisecurity.providers.dao.DaoAuthenticationProvider; //import org.acegisecurity.providers.anonymous.AnonymousAuthenticationProvider; //import org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache; //import org.acegisecurity.userdetails.jdbc.JdbcDaoImpl; //import org.acegisecurity.userdetails.jdbc.JdbcDaoImpl; //import org.acegisecurity.context.SecurityContextImpl; //import org.acegisecurity.vote.AffirmativeBased; //import org.acegisecurity.userdetails.jdbc.JdbcDaoImpl; //import org.springframework.jdbc.datasource.DataSourceTransactionManager; //import org.springframework.jdbc.core.JdbcTemplate; //import org.springframework.beans.factory.config.CustomEditorConfigurer; //import org.acegisecurity.intercept.method.MethodDefinitionSource; //import org.springframework.transaction.interceptor.TransactionInterceptor; //import org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor; //import org.acegisecurity.providers.*; public class WelcomeAdvice implements MethodBeforeAdvice { public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("hihihihihi,啟動MethodBeforeAdvice "); String userName; SecurityContext ctx = SecurityContextHolder.getContext(); if(ctx!=null) { System.out.println("ctx is not null"); Authentication auth = ctx.getAuthentication(); if(auth!=null) { System.out.println("auth is not null!"); Object principal=auth.getPrincipal(); if(principal instanceof UserDetails) { //輸出用戶名 System.out.println( "UserName is "+((UserDetails)principal).getUsername()); //密碼獲得 System.out.println( "UserPassword is "+((UserDetails)principal).getPassWord()); }else{ System.out.println("pricipal.toString()=="+principal.toString()); } } else { System.out.println("auth is null!"); } } else { System.out.println("ctx is null"); } userName = SecurityContextHolder.getContext().getAuthentication().toString(); System.out.println("UserName is:"); System.out.println(userName); System.out.println("++++++++++++++++++"); //輸出被攔截的接口的名稱 System.out.println(target.getClass().getName()); System.out.println(method.getName()); for(int i=0;i〈args.length;i++) { System.out.println("HI:"+(String)args[i]); //輸出傳入的字符串參數的值 System.out.println( args[i] .getClass().toString()); } // //Logger song = Logger.getLogger(target.getClass()); //得到目標類的logger //System.out.println("println:"+target.getClass().getName()+" did "+method.getName()); } } 說明:當接口TestAOPInterface的方法被調用前,WelcomeAdvice類作為此接口的前置通知,其before方法將先於TestAOPInterface的方法(在aoptest.XML中配置了WelcomeAdvice攔截TestAOPInterface的所有方法),在before方法裡實現了取得當前會話的Authentication即當前用戶的身份。既然取得了用戶的登錄Id,就可以利用此Id做更細粒度的權限控制及添加系統操作日志(日志需要記錄哪個用戶調用了此方法),由於acegi的這種授權體系支持到方法級別,可以在Advice中進行更深入的開發-規則引擎的開發。例如在ERP系統中,A庫管員和B庫管員都可以操作入庫單錄入功能,但A庫管員是原料庫庫管員,B是成品庫庫管員,如果成品庫庫管員錄入了原料庫物品,則提交入庫單操作失敗,這樣的需求,只提供方法及的授權還不夠,可通過開發規則引擎來支持數據級的授權。實現方式可將限制參數於傳入的方法實際參數進行比較,如符合規則則繼續執行。 如果大家想在業務方法裡而不是advce類裡獲得userId,最好是參照上述代碼,單獨設計一個類封裝acegi的調用,以免在業務方法中直接引入acegi的API造成對acegi的過度依賴。 2.4 DataSourceMethodDefinitionSourceEditor類 package com.mysoft.pmi.security; import org.acegisecurity.intercept.method.MethodDefinitionMap; import org.acegisecurity.ConfigAttributeEditor; import org.acegisecurity.ConfigAttributeDefinition; import java.util.List; import java.util.Iterator; import java.util.Map; import java.beans.PropertyEditorSupport; import org.springframework.jdbc.core.support.JdbcDaoSupport; import org.springframework.jdbc.core.JdbcTemplate; /** * 〈br〉Title: * 〈br〉Description: * 〈br〉Copyright: Copyright.com (c) 2003 * 〈br〉Company: * History: * create 2005-11-7 19:02:16 * * @author youlq * @version 1.0 */ public class DataSourceMethodDefinitionSourceEditor extends PropertyEditorSupport{ protected JdbcTemplate jdbcTemplate; //~ Methods ================================================================ public JdbcTemplate getJdbcTemplate(){ return jdbcTemplate; } public void setJdbcTemplate(JdbcTemplate jdbcTemplate){ this.jdbcTemplate=jdbcTemplate; } public void setAsText(String s) throws IllegalArgumentException{ MethodDefinitionMap source=new MethodDefinitionMap(); List rs; try{ rs=jdbcTemplate.queryForList(s); } catch(Exception e){ setValue(source); e.printStackTrace(); return; } if((rs==null)||rs.size()==0){ // Leave value in property editor null } else{ // Now we have properties, process each one individually ConfigAttributeEditor configAttribEd=new ConfigAttributeEditor(); for(Iterator iter=rs.iterator();iter.hasNext();){ Map row=(Map)iter.next(); String authority=(String)row.get("AUTHORITY"); String resource=(String)row.get("PROTECTED_RES"); if((null==authority)||(resource==null)){ continue; } // Convert value to serIEs of security configuration attributes configAttribEd.setAsText(authority); ConfigAttributeDefinition attr=(ConfigAttributeDefinition)configAttribEd .getValue(); // Register name and attribute source.addSecureMethod(resource, attr); } } setValue(source); } } 第三章 測試頁面 3.1 登錄頁面 acegilogin.JSP(將此文件放在web應用的根目錄下): 〈%@ page contentType="text/html;charset=GBK" language="java" %〉 〈%@ page import="org.acegisecurity.ui.AbstractProcessingFilter" %〉〈%@ page import="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter" %〉〈%@ page import="org.acegisecurity.AuthenticationException" %〉 〈html〉 〈head〉 〈title〉Login〈/title〉 〈/head〉 〈body〉 〈h1〉Login〈/h1〉 〈% String login_error=request.getParameter("login_error"); if(login_error!=null){ %〉 〈font color="red"〉 登錄失敗。原因: 〈%= ((AuthenticationException) session.getAttribute(AbstractProcessingFilter.ACEGI_SECURITY_LAST_EXCEPTION_KEY)).getMessage() %〉 〈/font〉〈% } %〉 〈form action="j_acegi_security_check" method="POST"〉 〈table〉 〈tr〉〈td〉User:〈/td〉〈td〉〈input type='text' name='j_username'〉〈/td〉〈/tr〉 〈tr〉〈td〉Password:〈/td〉〈td〉〈input type='password' name='j_passWord'〉〈/td〉〈/tr〉 〈tr〉〈td colspan='2'〉〈input name="submit" type="submit"〉〈/td〉〈/tr〉 〈tr〉〈td colspan='2'〉〈input name="reset" type="reset"〉〈/td〉〈/tr〉 〈/table〉 〈/form〉 〈/body〉〈/Html〉 說明:“一餐三碗”提供的數據庫表中的root用戶口令為root。 3.2 登錄後進入index.jsp 在web應用根目錄下建立一個secure目錄,此目錄下件一個index.JSp文件:〈html〉〈body〉〈h1〉Secure Page〈/h1〉 This is a protected page. You can get to me if you've been remembered, or if you've authenticated this session. 〈p〉〈a href="../testacegi.JSP"〉Home〈/a〉〈p〉〈a href="../j_acegi_logout"〉Logout〈/a〉〈/body〉〈/Html〉 3.3 調用業務方法的頁面在web應用的根目錄下建立一個testacegi.JSp文件: 〈%@ page import="org.acegisecurity.context.SecurityContextHolder" %〉〈%@ page import="org.acegisecurity.Authentication" %〉〈%@ page import="org.acegisecurity.ui.AccessDenIEdHandlerImpl" %〉〈%@ page import="org.springframework.aop.framework.ProxyFactoryBean"%〉〈%@ page import="org.springframework.context.ApplicationContext"%〉〈%@ page import="org.springframework.context.support.ClassPathXmlApplicationContext"%〉〈%@ page import="com.mysoft.pmi.testcase.aop.TestAOPInterface"%〉 〈% System.out.println("_______"+SecurityContextHolder.getContext().getAuthentication().toString()); ApplicationContext context1 = new ClassPathXmlApplicationContext("com/mysoft/pmi/resource/aoptest.XML"); TestAOPInterface bean1 = (TestAOPInterface)context1.getBean("aopProxyFactoryBean"); bean1.test("aa1a","ccc"); bean1.testAAA("dd1d","dwdwsd"); %〉 〈% Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (auth != null) { %〉 Authentication object as a String: 〈%= auth.toString() %〉〈BR〉〈BR〉〈% } %〉 說明:本文的例子是在Bea Weblogic8.1下測試通過。 JSP測試流程:在登錄頁面輸入正確的用戶名和口令(如果口令錯誤,控制台會報bad credentials的錯誤),登錄成功後,浏覽器打開了secure/index.jsp頁面,點此頁面的home連接,此連接調用了testacegi.JSp,此JSP調用了TestAOPInterface的接口方法,可以看到後台顯示了當前的userId(見WelcomeAdvice.Java)。如果需要將口令進行md5加密,然後與數據庫中加密的口令進行比較,可參考applicationContext-acegi-security.XML。