??????最近兩星期在學習acegi,過程中感謝JavaEye,SpringSide和在網上提供acegi學習心得的網友們。
為了加深自己的認識,準備寫下一些DEMO,希望可以給準備學習acegi的同學一些幫助。
??????作為安全服務離不開認證和授權這兩個主要組成部分。而這篇文章就是針對acegi的認證服務。
??????????????????????????????????????????????????????????????????????????????學習Acegi-認證(authentication)
代碼環境基于:
JDK1.5
acegi1.0.3
spring2.0
IDE基于:
Eclipse3.2+MyEclipse5.0.1
面向人員:
熟悉Eclipse+MyEclipse開發但剛開始了解acegi的人員。如果你是高手請指出文章不足之處。
1.建立一個MyEclipse的WebProject,把下列jar文件拷貝到項目的WEB-INF/lib目錄:
acegi-security-1.0.3.jar
spring2.0.jar
commons-codec-1.3.jar
費話說一句(占些字數):這是因為代碼運行需要這些包的支持。
2.修改WEB-INF下的web.xml文件,內容如下:
< web - app?version = " 2.4 " ?xmlns = " http://java.sun.com/xml/ns/j2ee "
????xmlns:xsi = " http://www.w3.org/2001/XMLSchema-instance "
????xsi:schemaLocation = " http://java.sun.com/xml/ns/j2ee?
????http: // java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
???? < display - name > acegi?Example?of?liuyxit </ display - name >
????
???? <!--
????????定義應用的上下文參數,用于ContextLoaderListener??????
???? -->
???? < context - param >
???????? < param - name > contextConfigLocation </ param - name >
???????? < param - value >
????????????classpath:spring / applicationContext.xml
???????? </ param - value >
???? </ context - param >
???? <!-- acegi?的filter鏈代理 -->
???? < 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>
????<!--
????????裝載應用軟件的Spring上下文
????????要由WebapplicationContextUtils.getWebApplicationnContext(servletContext)得到.
????-->
????<listener>
????????<listener-class>
????????????org.springframework.web.context.ContextLoaderListener
????????</listener-class>
????</listener>
</web-app>
其中FilterChainProxy實現了filter接口,它主要是實例化FilterChainProxy,并把所有動作交由FilterChainProxy處理。這樣簡化了web.xml的配置,并且充分利用了Spring IOC管理Bean的優勢。
3.在src目錄右鍵新建一個resource folder,在下面再建立acegi和spring目錄
在spring目錄中創建applicationContext.xml文件,內容:
< beans?xmlns = " http://www.springframework.org/schema/beans "
???????xmlns:xsi = " http://www.w3.org/2001/XMLSchema-instance "
???????xmlns:aop = " http://www.springframework.org/schema/aop "
???????xmlns:tx = " http://www.springframework.org/schema/tx "
???????xsi:schemaLocation = " http://www.springframework.org/schema/beans?http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
???????????http: // www.springframework.org/schema/aop? http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
???????????http: // www.springframework.org/schema/tx? http://www.springframework.org/schema/tx/spring-tx-2.0.xsd "
??????? default - autowire = " byName " ? default - lazy - init = " true " >
????
???? <!-- ? ======================== ?FILTER?CHAIN? ======================= ? -->
???? < bean?id = " filterChainProxy " ? class = " org.acegisecurity.util.FilterChainProxy " >
?????? < property?name = " filterInvocationDefinitionSource " >
????????? < value >
????????????CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
????????????PATTERN_TYPE_APACHE_ANT
???????????? /** =authenticationProcessingFilter,exceptionTranslationFilter
?????????</value>
??????</property>
????</bean>???
????<!--?========================?認證filter?=======================?-->
???
????<!--?表單認證處理filter?-->
????<bean?id="authenticationProcessingFilter"?class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
????????<property?name="authenticationManager"?ref="authenticationManager"/>
????????<property?name="authenticationFailureUrl"?value="/acegilogin.jsp?login_error=1"/>
????????<property?name="defaultTargetUrl"?value="/userinfo.jsp"/>
????????<property?name="filterProcessesUrl"?value="/j_acegi_security_check"/>
????</bean>????
???????
???<!--?認證管理器?-->
???<bean?id="authenticationManager"?class="org.acegisecurity.providers.ProviderManager">
??????<property?name="providers"><!--?可有多個認證提供器,其中一個證通過就可以了?-->
?????????<list>
????????????<ref?local="daoAuthenticationProvider"/>
????????????<ref?local="rememberMeAuthenticationProvider"/>
?????????</list>
??????</property>
???</bean>???
????<bean?id="daoAuthenticationProvider"?class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
????????<property?name="userDetailsService"?ref="inMemoryDaoImpl"/>????????
????</bean>????
????<!-- 用戶資料-->
?????<bean?id="inMemoryDaoImpl"?class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">??
????????<property?name="userMap">??
????????????<value>??
????????????????liuyxit=123,ROLE_SUPERVISOR
????????????????user1=user1,ROLE_USER
????????????????user2=user2,disabled,ROLE_USER???
????????????</value>??
????????</property>??
????</bean>
????<!--?異常處理filter?-->
????<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>????
</beans>
其中filterChainProxy就是由web.xml聲明的filter(FilterToBeanProxy)的targetClass。它主要是裝載filterInvocationDefinitionSource指定的filter類(例子中為authenticationProcessingFilter,exceptionTranslationFilter),并順序調用它們的doFilter方法,進行安全服務處理。
而authenticationProcessingFilter是處理一個認證表單,登陸用的表單必須提交用戶名和密碼這兩個參數給這個filter.由用戶名和密碼構造一個UsernamePasswordAuthenticationToken,將傳給AuthenticationManager的authenticate方法進行認證處理。該filter默認處理filterProcessesUrl屬性指定的URL,認證失敗會轉到authenticationFailureUrl,認證成功會轉到defaultTargetUrl頁面。
AuthenticationManager顧名思義認證管理器,它只有一個接口方法authenticate用于返回認證結果,他的實現類由多個AuthenticationProvider進行投票,決定認證是否通過。
daoAuthenticationProvider是檢驗用戶錄入的認證數據是否正確(說白了就是用戶名和密碼是否正確)
inMemoryDaoImpl是給daoAuthenticationProvider提供系統的用戶資料。而資料的來源是從配置中裝載到內存的。
當認證不通過時,AuthenticationManager的實現類AbstractAuthenticationManager會拋出AuthenticationException類型的異常。這時排在最后的exceptionTranslationFilter會捕獲該異常,并轉向authenticationEntryPoint。
4.在WebRoot下創建index.jsp(其實不要也沒關系,主要是為了方便),直接轉向用戶資料顯示頁。內容如下
<! DOCTYPE?HTML?PUBLIC? " -//W3C//DTD?HTML?4.0?Transitional//EN " >
< html >
< head >
??? <!-- ?
??????? < META?HTTP - EQUIV = " Refresh " ?CONTENT = " 0;URL=user!list.rgb " >
???? --> ?
???? < META?HTTP - EQUIV = " Refresh " ?CONTENT = " 0;URL=userinfo.jsp " >
</ head >
< body >
< p > Loading?

</ body >
</ html >
5.在WebRoot下創建userinfo.jsp,用于顯示當前登陸的用戶信息。內容如下
<% @?page? import = " org.acegisecurity.context.SecurityContextHolder " %>
<% @?page? import = " org.acegisecurity.userdetails.* " %>
<%
????String?path? = ?request.getContextPath();
????String?basePath? = ?request.getScheme()? + ? " :// "
???????????? + ?request.getServerName()? + ? " : " ? + ?request.getServerPort()
???????????? + ?path? + ? " / " ;
%>
<! DOCTYPE?HTML?PUBLIC? " -//W3C//DTD?HTML?4.01?Transitional//EN " >
< html >
???? < head >
???????? < base?href = " <%=basePath%> " >
???????? < title > My?JSP? ' pass.jsp ' ?starting?page </ title >
???????? < meta?http - equiv = " pragma " ?content = " no-cache " >
???????? < meta?http - equiv = " cache-control " ?content = " no-cache " >
???????? < meta?http - equiv = " expires " ?content = " 0 " >
???????? < meta?http - equiv = " keywords " ?content = " keyword1,keyword2,keyword3 " >
???????? < meta?http - equiv = " description " ?content = " This?is?my?page " >
???????? <!--
???? < link?rel = " stylesheet " ?type = " text/css " ?href = " styles.css " >
???? -->
???? </ head >
???? < body >
????????當前用戶:
???????? <%
????????????Object?obj? = ?SecurityContextHolder.getContext().getAuthentication();????????
???????????? if ?( null ? != ?obj){
????????????????Object?userDetail? = ?SecurityContextHolder.getContext().getAuthentication().getPrincipal();
????????????????String?username? = ? "" ;
???????????????? if ?(userDetail? instanceof ?UserDetails)?{
????????????????????username? = ?((UserDetails)?userDetail).getUsername();
????????????????}? else ?{
????????????????????username? = ?userDetail.toString();
????????????????}
????????????????out.print(username);
????????????????out.print( " <br><a?href=\ " j_acegi_logout\ " >注銷</a> " );
????????????} else {
????????????????out.print( " 當前沒有有效的用戶 " );
????????????????out.print( " <br><a?href=\ " acegilogin.jsp\ " >登陸</a> " );
????????????}
???????? %>
????????
????????
???? </ body >
</ html >
?
6.在WebRoot下創建acegilogin.jsp
<% @?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 >
???? < P > Valid?users:
???? < P >
???? < P > username? < b > liuyxit </ b > ,?password? < b > 123 </ b > ?(supervisor)
???? < P > username? < b > user1 </ b > ,?password? < b > user1 </ b > ?(normal?user)
???? < p > username? < b > user2 </ b > ,?password? < b > user2 </ b > ?(user?disabled)
???? < p >
???? <%
????????String?strError? = ?request.getParameter( " login_error " );
????????
???????? if ?( null ? != ?strError){?
????? %>
?????? < font?color = " red " >
????????你的登陸失敗,請重試。 < BR >< BR >
?????????原因:? <%= ?((AuthenticationException)?session.getAttribute(AbstractProcessingFilter.ACEGI_SECURITY_LAST_EXCEPTION_KEY)).getMessage()? %>
?????? </ font >
?????? <%
??????????} // end?if
?????? %>
???? < form?action = " j_acegi_security_check " ?method = " POST " >
?????? < table >
???????? < tr >< td > User: </ td >< td >< input?type = ' text ' ?name = ' j_username ' ?value = ' <%=?session.getAttribute(AuthenticationProcessingFilter.ACEGI_SECURITY_LAST_USERNAME_KEY)?%> ' ></ td ></ tr >
???????? < tr >< td > Password: </ td >< td >< input?type = ' password ' ?name = ' j_password ' ></ td ></ tr >
???????? < tr >< td >< input?type = " checkbox " ?name = " _acegi_security_remember_me " ></ td >< td > 2周內自動登錄 </ 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 >
7.OK,發布項目,訪問http://localhost:8080/acegiexample
這時index.jsp會自動轉向userinfo.jsp,由于還沒有用戶登錄,所以沒有資料顯示。按登陸鏈接進入登錄頁,登錄成功后會看到顯示用戶名的頁面(當然可以有更多的用戶資料,但這僅僅是example),不成功時會在登錄頁提示信息。我們可以用user1和user2登陸,可以分別測試登錄成功和失敗的流程。
8.可以看到登錄頁上有自動登陸功能,而userinfo.jsp頁有注銷功能。但還是不起作用,好,現在我們馬上首手加入這兩個功能??碼cegi可以方便到什么程度。在applicationContext.xml中加入如下紅色部分:
< beans?xmlns = " http://www.springframework.org/schema/beans "
???????xmlns:xsi = " http://www.w3.org/2001/XMLSchema-instance "
???????xmlns:aop = " http://www.springframework.org/schema/aop "
???????xmlns:tx = " http://www.springframework.org/schema/tx "
???????xsi:schemaLocation = " http://www.springframework.org/schema/beans?http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
???????????http: // www.springframework.org/schema/aop? http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
???????????http: // www.springframework.org/schema/tx? http://www.springframework.org/schema/tx/spring-tx-2.0.xsd "
??????? default - autowire = " byName " ? default - lazy - init = " true " >
????
???? <!-- ? ======================== ?FILTER?CHAIN? ======================= ? -->
???? < bean?id = " filterChainProxy " ? class = " org.acegisecurity.util.FilterChainProxy " >
?????? < property?name = " filterInvocationDefinitionSource " >
????????? < value >
????????????CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
????????????PATTERN_TYPE_APACHE_ANT
???????????? /** =authenticationProcessingFilter,logoutFilter,rememberMeProcessingFilter,exceptionTranslationFilter
?????????</value>
??????</property>
????</bean>???
????<!--?========================?認證filter?=======================?-->
???
????<!--?表單認證處理filter?-->
????<bean?id="authenticationProcessingFilter"?class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
????????<property?name="authenticationManager"?ref="authenticationManager"/>
????????<property?name="authenticationFailureUrl"?value="/acegilogin.jsp?login_error=1"/>
????????<property?name="defaultTargetUrl"?value="/userinfo.jsp"/>
????????<property?name="filterProcessesUrl"?value="/j_acegi_security_check"/>
????</bean>
????
????<!--?利用cookie自動登陸filter?-->
????<bean?id="rememberMeProcessingFilter"
??????????class="org.acegisecurity.ui.rememberme.RememberMeProcessingFilter">
????????<property?name="authenticationManager"
??????????????????ref="authenticationManager"/>
????????<property?name="rememberMeServices"?ref="rememberMeServices"/>
????</bean>????
????<bean?id="rememberMeServices"
??????????class="org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices">
????????<property?name="userDetailsService"?ref="inMemoryDaoImpl"/>
????????<property?name="key"?value="javargb"/>
????</bean>??
????<bean?id="rememberMeAuthenticationProvider"
??????????class="org.acegisecurity.providers.rememberme.RememberMeAuthenticationProvider">
????????<property?name="key"?value="javargb"/>
????</bean>???
????
????<!--?注銷處理filter?-->
????<bean?id="logoutFilter"?class="org.acegisecurity.ui.logout.LogoutFilter">
??????<constructor-arg?value="/acegilogin.jsp"/>?<!--?URL?redirected?to?after?logout?-->
??????<constructor-arg>
?????????<list>
??????????????<ref?bean="rememberMeServices"/>
??????????????<bean?class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler"/>
?????????</list>
??????</constructor-arg>
???</bean>
????
???<!--?認證管理器?-->
???<bean?id="authenticationManager"?class="org.acegisecurity.providers.ProviderManager">
??????<property?name="providers"><!--?可有多個認證提供器,其中一個證通過就可以了?-->
?????????<list>
????????????<ref?local="daoAuthenticationProvider"/>
????????????<ref?local="rememberMeAuthenticationProvider"/>
?????????</list>
??????</property>
???</bean>???
????<bean?id="daoAuthenticationProvider"?class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
????????<property?name="userDetailsService"?ref="inMemoryDaoImpl"/>????????
????</bean>????
????<!--?
????<bean?id="inMemoryDaoImpl"?class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
????????<property?name="userProperties">
????????????<bean?class="org.springframework.beans.factory.config.PropertiesFactoryBean">
????????????????<property?name="location"?value="classpath:acegi/users.properties"/>
????????????</bean>
????????</property>
????</bean>
?????-->
?????<bean?id="inMemoryDaoImpl"?class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">??
????????<property?name="userMap">??
????????????<value>??
????????????????liuyxit=123,ROLE_SUPERVISOR
????????????????user1=user1,ROLE_USER
????????????????user2=user2,disabled,ROLE_USER???
????????????</value>??
????????</property>??
????</bean>
????<!--?異常處理filter?-->
????<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>????
</beans>
另要注意:Acegi默認的自動登陸設定參數名為_acegi_security_remember_me,注銷鏈接為/j_acegi_logout。
馬上重啟Tomcat測試看看^_^。
9.通常用戶資料會放在數據庫中,而不會放在配置文件,接著下面我們來再行修改一下。
首先創建要用到的用戶表和權限表,并插入初始化數據:
USERNAME?VARCHAR( 50 )?NOT?NULL?PRIMARY?KEY,
PASSWORD?VARCHAR( 50 )?NOT?NULL,
ENABLED?BIT?NOT?NULL)
INSERT?INTO?USERS(username,password,enabled)?values( ' liuyxit ' , ' 123 ' , ' 1 ' )
INSERT?INTO?USERS(username,password,enabled)?values( ' user1 ' , ' user1 ' , ' 1 ' )
INSERT?INTO?USERS(username,password,enabled)?values( ' user2 ' , ' user2 ' , ' 0 ' )
CREATE?TABLE?AUTHORITIES(
USERNAME?VARCHAR( 50 )?NOT?NULL,
AUTHORITY?VARCHAR( 50 )?NOT?NULL,
CONSTRAINT?FK_AUTHORITIES_USERS?FOREIGN?KEY(USERNAME)?REFERENCES?USERS(USERNAME)
);
INSERT?INTO?AUTHORITIES(USERNAME,AUTHORITY)?values( ' liuyxit ' , ' ROLE_SUPERVISOR ' )
INSERT?INTO?AUTHORITIES(USERNAME,AUTHORITY)?values( ' user1 ' , ' ROLE_USER ' )
INSERT?INTO?AUTHORITIES(USERNAME,AUTHORITY)?values( ' user2 ' , ' ROLE_USER ' )
這里我用的是acegi默認的數據結構,可以改只要你指定JdbcDaoImpl的authoritiesByUsernameQuery和usersByUsernameQuery屬性就可以了。另AUTHORITIES表也要一同加入,原因acegi獲得userDetail時,也會讀取這個表的內容,否則會拋“nested exception is java.sql.SQLException: 對象名 'authorities' 無效”這個異常。
修改applicationContext.xml,變成如下:











































































































這時userDetailsService是通過實現類jdbcDaoImpl從數據庫獲得用戶資料來作認證比較。
OK,大功告成。
后記:很少寫技術文章,除了要堅持之外,文筆和思路都很重要。感覺自己的寫作水平太差了,希望大家指出不合理的地方。有時間我會再寫后篇《學習Acegi-授權(authorization)》,感謝大家把拙文看完,TKS!