這裏只列出常用的過濾器和攔載器
1. 過濾器:HttpSessionContextIntegrationFilter,authenticationProcessingFilter,BasicProcessingFilter,RememberMeProcessingFilter,anonymousProcessingFilter,exceptionTranslationFilter
2. 攔截器:filterSecurityInterceptor(其實它是過濾器,不過把它放在這裏更能說明它的功能),methodSecurityInterceptor
看着上面的用紅色標出的過濾器是用來認證(表單和HTTP基本認證,當然還有別的不過這兩個比較長用)它們是資源訪問的入口.其它的過濾器是用來輔助的:HttpSessionContextIntegrationFilter是用來把認證信息記錄到Session中的RememberMeProcessingFilter是以cookie的形式來保存認證信息的. anonymousProcessingFilter是在匿名的時候(這時候是沒有認證信息的)給這個用戶分配一個匿名的認證信息,exceptionTranslationFilter總結一下異常並處理.在實際中選擇適合程序的即可.
上面只是資源訪問的入口,真正保護資源的是這兩個攔截器:filterSecurityInterceptor,攔截URL的類(它是個過濾器)
metohdSecurityInterceptor,攔截類中方法的調用,它們爲什麼要攔截呢?就是想在訪問或調用這些方法之前來判斷一下用戶是否有訪問或調用的權限,有就通過,沒有就踢出.
除此之外,Acegi專門做了兩個管理器(實際上就是兩個類,爲什麼會用做這兩個管理器,因爲認證和授權都有一些的操作,這就需要專門做兩個管理器了):authenticationManager(class= org.acegisecurity.providers.ProviderManager),授權管理器accessDecisionManager(class=org.acegisecurity.vote.AffirmativeBased)
說白了一個用於認證用戶,一個是用於權限的授於的
先來說認證用戶,認證管理器有什麼東西呢?只內置了一些提供者:這些提供者呢又是什麼呢,他們是提供用戶的驗證身份信息的,比如從數據庫或配置文件裏讀出用戶名和密碼,在用戶的cookie裏讀出身份信息(rememberMeProcessingFilter用到的[前面講了的,有印象吧]),或在Session裏讀出身份驗證信息(HttpSessionContextIntegrationFilter起作用的),這裏我們只說一下從數據庫或配置文件裏讀出用戶名密碼來裝配驗證信息的,其它的配置類似可以找一下對應api在Spring裏配置即可,daoAuthenticationProvider是數據庫的提供者class=org.acegisecurity.providers.dao.DaoAuthenticationProvider,而它提供的服務呢又有幾種,數據庫和配置文件(這是Acegi的兩個默認的實現)當然也可以自己實現(實現userDetailsService接口就行)
- <bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
- <property name="providers">
- <list>
- <ref local="daoAuthenticationProvider"/>
- </list>
- </property>
- </bean>
- <bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
- <!-- <property name="userDetailsService"><ref local="InMemoryDaoImpl"/></property> --><!-- 這裏有兩種選擇 -->
- <property name="userDetailsService"><ref local="jdbcDaoImpl"/></property>
- </bean>
如果用戶名和密碼在配置文件裏可以用InMemoryDaoImpl,class=org.acegisecurity.userdetails.memory.InMemoryDaoImpl,在這個類的userMap裏配置即可:javafish=java,ROLE_USER,配置了一個用戶名爲javafish,密碼爲java,用戶組爲ROLE_USER的用戶,不過最常用的還是數據庫的JDBC實現(兩個二選一)org.acegisecurity.userdetails.jdbc.JdbcDaoImpl裏面需要usersByUsernameQuery和authoritiesByUsernameQuery還有數據源dataSource(有人問爲什麼呢,userByUsernameQuery是用來通過用戶名來查密碼的,authoritiesByUsernameQuery是用來通過用戶名來查權限的,查詢數據庫肯定的用數據源吧這個裏是用的SpringFrameWork的DataSource)它們查詢的sql語句是有講究的,就是查密碼的時候查三個第一個是username,第二個是password,第三個是是否可用,查權限的時候查兩個:username和authorities(具體看例子)
- <bean id="InMemoryDaoImpl" class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
- <property name="userMap">
- <value>
- javajavafish=java,ROLE_USER
- </value>
- </property>
- </bean>
- <bean id="jdbcDaoImpl" class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl">
- <property name="usersByUsernameQuery">
- <value>select username,password,enabled from users where username=?</value>
- </property>
- <property name="authoritiesByUsernameQuery">
- <value>select username,authority from authorities where username=?</value>
- </property>
- <property name="dataSource">
- <ref local="dataSource"/>
- </property>
- </bean>
- <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
- <property name="driverClassName">
- <value>com.mysql.jdbc.Driver</value>
- </property>
- <property name="url">
- <value>jdbc:mysql://localhost:3306/test</value>
- </property>
- <property name="username">
- <value>root</value>
- </property>
- <property name="password">
- <value>javafish</value>
- </property>
- </bean>
下面說一下授權,授權管理器又有什麼東西呢?accessDecisionManager,Acegi把授權方面弄的比較的形象化,把某個URL或方法是否可以被訪問按投票的形式來決定,
Acegi提出來了幾種方案:
1. 如果有一個贊成就同意(具體的說就是隻要你在那個URL對應的幾個用戶組中的一個就讓你訪問)
2. 如果都贊成就同意(具本的說就是那個URL對應的幾個用戶組裏都有你,你才能訪問)
3. 如果都不反對就同意(這個在下面講投票者的時候再說)
- <bean id="accessDecisionManager" class="org.acegisecurity.vote.AffirmativeBased">
- <property name="allowIfAllAbstainDecisions"><!-- 是否讓全部棄權的通過 -->
- <value>false</value>
- </property>
- <property name="decisionVoters"><!-- 投票者們 -->
- <ref bean="roleVoter"/>
- </property>
- </bean>
而投票者呢:Acegi自己實現了一個投票者的類RoleVoter:
現在我用第一種方案,RoleVoter只是在URL對應的用戶組裏有ROLE_爲前綴的才進行投票,否則的話棄權.(我們也可以在配置RoleVoter的時候把ROLE_配置成爲別的前綴如JAVA_),分別對URL對應的每個用戶組投票,如果用戶在這個用戶組裏就投贊成,不在投反對(在用戶組的前綴是ROLE_的前提下)這樣就不難體會第三種方案的用途了吧
- <bean id="roleVoter" class="org.acegisecurity.vote.RoleVoter">
- <property name="rolePrefix">
- <value>ROLE_</value><!-- 可以改成別的 -->
- </property>
- </bean>
這樣認證管理器和授權管理器就ok了,別的無論是過濾器還是攔截器都會用到它們兩個,因爲它們都要驗證而這兩個就是憑證.
那麼那兩個訪問過濾器呢,先說authenticationProcessingFilter是用於表單登陸的
- <bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
- <property name="authenticationManager"><ref bean="authenticationManager"/></property>
- <property name="authenticationFailureUrl"><value>/failure.html</value></property><!--登陸失敗轉向的頁面 -->
- <property name="defaultTargetUrl"><value>/ok.html</value></property><!-- 登陸成功轉向的頁面 -->
- <property name="filterProcessesUrl"><value>/check</value></property><!-- 要驗證的地址 -->
- </bean>
這樣的話加上上面配置的認證管理器就已經可以處理登陸了(注意的是它沒有用到授權管理器,因爲它只是個訪問入口還沒有權限的授予)
再說一下HTTP基本認證:它比上面的略複雜一點
需要配置一個
- <bean id="BasicProcessingFilterEntryPoint" class="org.acegisecurity.ui.basicauth.BasicProcessingFilterEntryPoint">
- <property name="realmName"><value>javafish</value></property><!-- 基本認證對話框上顯示的字 -->
- </bean>
- 然後
- <bean id="BasicProcessingFilter" class="org.acegisecurity.ui.basicauth.BasicProcessingFilter">
- <property name="authenticationManager">
- <ref bean="authenticationManager"/>
- </property>
- <property name="authenticationEntryPoint">
- <ref bean="BasicProcessingFilterEntryPoint"/>
- </property>
- </bean>
即可.
不過在HTTP基本認證裏需要注意的地方是:好多人配置好了怎麼看不到效果啊,一開始我也是很鬱悶,看了BasicProcessingFilter的源代碼:
String header = httpRequest.getHeader("Authorization");//我們一般進入網頁測試的時候這裏的header始終是null的
- if (logger.isDebugEnabled()) {
- logger.debug("Authorization header: " + header);
- }
- if ((header != null) && header.startsWith("Basic ")) {//從這裏可以看到一般的登陸基本認證是不起作用的
- .................
只有在服務器上配置哪個目錄在訪問的時候用HTTP基本認證,它纔會起作用(一開始還以爲是Acegi的BUG呢)
下面說一下真正對URL資源的保護了filterSecurityInterceptor它的本質是個過濾器,有了前面*管理器的基礎了這就很容易了:
- <bean id="filterSecurityInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
- <property name="authenticationManager">
- <ref local="authenticationManager"/>
- </property>
- <property name="accessDecisionManager">
- <ref local="accessDecisionManager"/>
- </property>
- <property name="objectDefinitionSource"><!-- 把URL和可訪問的用戶組對應起來 -->
- <value>
- CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON<!-- 把URL全部轉化爲小寫 -->
- PATTERN_TYPE_APACHE_ANT<!-- 以ANT的形式來配置路徑 -->
- /ok.html=ROLE_USER
- </value>
- </property>
- </bean>
光這樣配置還是不夠的,因爲當授權失敗的時候會拋出異常的,我們應該配置一個異常過濾器來捕獲它,exceptionTranslationFilter它是用來捕獲異常的,看一下配置吧:
- <bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">
- <property name="authenticationEntryPoint"><ref local="authenticationProcessingFilterEntryPoint"/></property>
- <property name="accessDeniedHandler">
- <bean class="org.acegisecurity.ui.AccessDeniedHandlerImpl">
- <property name="errorPage" value="/failure.html"/><!-- 發生異常轉向的網頁 -->
- </bean>
- </property>
- </bean>
- <bean id="authenticationProcessingFilterEntryPoint" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
- <property name="loginFormUrl"><value>/Login.html</value></property><!-- 得到表單的信息 -->
- <property name="forceHttps"><value>false</value></property><!-- 不用https -->
- </bean>
這樣就OK了
最後說一下對類中方法的保護:
首先寫一個類並在spring中配置好:
- package org.li.acegi;
- public class TestAcegi
- {
- public void Role()
- {
- System.out.println("javafish");
- }
- }
- <bean id="testAcegi" class="org.li.acegi.TestAcegi"/>
然看寫個servlet訪問一下它
- package org.li.servlet;
- import java.io.IOException;
- import java.io.PrintWriter;
- import javax.servlet.ServletException;
- import javax.servlet.http.HttpServlet;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import org.li.acegi.TestAcegi;
- import org.springframework.context.ApplicationContext;
- import org.springframework.web.context.support.WebApplicationContextUtils;
- public class TestServlet extends HttpServlet
- {
- private static final long serialVersionUID = -5610016980827214773L;
- public void doGet(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException
- {
- response.setContentType("text/html;charset=GBK");
- PrintWriter out = response.getWriter();
- ApplicationContext ctx =
- WebApplicationContextUtils.getRequiredWebApplicationContext(request.getSession().getServletContext());
- TestAcegi test = (TestAcegi)ctx.getBean("testAcegi");
- test.Role();//訪問TestAcegi類的Role方法
- out.println("調用成功");
- }
- public void doPost(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException
- {
- doGet(request,response);
- }
- }
準備工作做好了,開始配置Acegi
先在Spring裏給Acegi做個代理:
- <bean id="autoProxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
- <property name="beanNames">
- <list>
- <value>testAcegi</value><!-- 要代理的Bean的id -->
- </list>
- </property>
- <property name="interceptorNames">
- <list>
- <value>methodSecurityInterceptor</value><!-- 代理爲... -->
- </list>
- </property>
- </bean>
裏面的methodSecurityInterceptor呢配置爲:
- <bean id="methodSecurityInterceptor" class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
- <property name="authenticationManager">
- <ref bean="authenticationManager"/>
- </property>
- <property name="accessDecisionManager">
- <ref bean="accessDecisionManager"/>
- </property>
- <property name="objectDefinitionSource"><!-- 對代理的類的方法開始配置權限 -->
- <value>org.li.acegi.TestAcegi.Role=ROLE_USER</value>
- </property>
- </bean>
這樣當直接訪問http://localhost:8080/AcegiWeb/servlet/TestServlet的時候會發現不可訪問,控件臺也不輸出”javafish”,當輸入正確的用戶名和密碼之後便可以訪問.
這樣它就對類的方法調用起了保護的作用,這一點可以把Acegi應用到DWR上效果是很理想的.
對於Acegi有很多的過濾器不用全寫在web.xml裏,acegi提供了一個特殊的過濾器我們可以寫成這樣,在Web.xml裏:
- <filter>
- <filter-name>Acegi</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-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
- <context-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>
- /WEB-INF/applicationContext.xml
- </param-value>
- </context-param>
- <listener>
- <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
- </listener>
- <listener>
- <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
- </listener>
- <listener>
- <listener-class>org.acegisecurity.ui.session.HttpSession