到現(xiàn)在我也沒有弄明白Acegi里面很多的功能,剛剛開始學(xué)的時(shí)候我就已經(jīng)被它那繁瑣的配置震懾住了,不過當(dāng)我動(dòng)起手來一步步實(shí)現(xiàn)的時(shí)候,才發(fā)現(xiàn)其實(shí)它遠(yuǎn)沒有那么難,當(dāng)然隨著學(xué)習(xí)的深入,會(huì)漸漸再發(fā)現(xiàn)這一點(diǎn)吧,現(xiàn)在就讓我們初學(xué)者一切體驗(yàn)Acegi的功能吧!
還以我傳統(tǒng)的例子為例:
畢業(yè)設(shè)計(jì)選題系統(tǒng),三種角色:教師,學(xué)生,管理員,我想讓他們的登陸都在一個(gè)界面下自動(dòng)識(shí)別,而無需進(jìn)行身份選擇,登陸后,他們將分別到各自的admin.jsp,stu.jsp,teacher.jsp
在數(shù)據(jù)庫中的表結(jié)構(gòu)如下(很多屬性略):
id--- user---password--type---about
type是用來存儲(chǔ)用戶的類別,分別有a,t,s分別對(duì)應(yīng)三種角色
about對(duì)應(yīng)的是acegi里所需要的enable,用戶是否可用
在model里,我們采用了繼承關(guān)系:
父類user:
package subject.model;
public abstract class User extends BaseObject
{
?private Integer id;
?private String user;
?private String password;
?private String name;
?private String telphone;
//set and get method?
?
?public abstract String getType(); //這個(gè)是用來反映用戶角色的關(guān)鍵函數(shù),在子類實(shí)現(xiàn),從而實(shí)現(xiàn)多態(tài)
}
子類的實(shí)現(xiàn):
======================
package subject.model;
import subject.Constants;
public class Teacher extends User
{
?private String level;???????? //教師的職稱
//set and get method
?public String getType()
?{
??return Constants.TEACHER;
?}
}
================
package subject.model;
import subject.Constants;
public class Student extends User
{
?private static final long serialVersionUID = 1L;
?private SchoolClass schoolClass;???????? //學(xué)生的班級(jí)
?private String sn;???????????? //學(xué)生的學(xué)號(hào)
//set and get method
?
?public String getType()
?{
??return Constants.STUDENT;
?}
}
=================
package subject.model;
import subject.Constants;
public class Admin extends User
{
?private String grade;?????????? //管理員的級(jí)別
//set and get method
?public String getType()
?{
??return Constants.ADMIN;
?}
}
對(duì)于三者所共有的屬性在數(shù)據(jù)庫里,都存在一個(gè)字段,而依據(jù)不同的角色擁有不同的含義,學(xué)生的班級(jí)則存放在了about里,只要學(xué)生有班級(jí),他就able,否則就enable了!而管理員和教師則默認(rèn)為1!
這種是屬于一個(gè)繼承樹存放在一個(gè)表的情況,Hibernate的配置如下:
<hibernate-mapping>
?<class name="subject.model.User" discriminator-value="not null">
??<id name="id">
???<generator class="increment" />
??</id>
??
??<discriminator column="type" type="character" />
??
??<property name="user" />
??<property name="password" />
??<property name="name" />
??<property name="telphone" />
??<subclass name="subject.model.Admin" discriminator-value="a">
???<property name="grade" column="sn" />
??</subclass>
??
??<subclass name="subject.model.Teacher" discriminator-value="t">
???<property name="level" column="sn" />
??</subclass>
??
??<subclass name="subject.model.Student" discriminator-value="s">
???
???<property name="sn" />
???
???<many-to-one name="schoolClass" class="subject.model.SchoolClass"
????column="about" update="false" insert="false" />
????
??</subclass>
?</class>
</hibernate-mapping>
=============================================
上面的這些都是模型的基礎(chǔ),下面再講怎么樣配合Spring和Acegi實(shí)現(xiàn)系統(tǒng)的安全與登陸
在Spring中Hibernate的配置只介紹不說明:
<!-- 定義DBCP數(shù)據(jù)源 -->
?<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
??<property name="driverClassName" value="com.mysql.jdbc.Driver" />
??<property name="url" value="jdbc:mysql://localhost/subject?useUnicode=true&characterEncoding=gbk" />
??<property name="username" value="root" />
??<property name="password" value="" />
??<property name="maxActive" value="100" />
??<property name="maxIdle" value="30" />
??<property name="maxWait" value="1000" />
??<property name="defaultAutoCommit" value="true" />
??<property name="removeAbandoned" value="true" />
??<property name="removeAbandonedTimeout" value="60" />
?</bean>
?<!-- Hibernate -->
?<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
??<property name="dataSource" ref="dataSource" />
??<property name="mappingResources">
???<list>
????<value>subject/model/User.hbm.xml</value>
???</list>
??</property>
??<property name="hibernateProperties">
???<props>
????<prop key="hibernate.dialect">org.hibernate.dialect.MySQLInnoDBDialect</prop>
???</props>
??</property>
?</bean>
?<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
??<property name="sessionFactory" ref="sessionFactory" />
?</bean>
<!-- Dao對(duì)象 -->
<bean id="userDao" class="subject.dao.hibernate.UserDaoImpl">
??<property name="sessionFactory" ref="sessionFactory" />
?</bean>
<!-- 業(yè)務(wù)邏輯 -->
?<bean id="txProxyTemplate" abstract="true" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
??<property name="transactionManager" ref="transactionManager" />
??<property name="transactionAttributes">
???<props>
????<prop key="save*">PROPAGATION_REQUIRED</prop>
????<prop key="remove*">PROPAGATION_REQUIRED</prop>
????<prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
???</props>
??</property>
?</bean>
<bean id="userManager" parent="txProxyTemplate">
??<property name="target">
???<bean class="subject.service.impl.UserManagerImpl">
????<property name="userDao" ref="userDao" />
???</bean>
??</property>
?</bean>
<!-- Struts -->
?<bean name="/user" class="subject.web.action.UserAction" singleton="false">
??<property name="userManager">
???<ref bean="userManager" />
??</property>
?</bean>
==================
上面具體的不用了解,無非就是調(diào)用和數(shù)據(jù)庫的操作,
下面就要對(duì)Acegi進(jìn)行聲明了:
我不用Ctrl+c和Ctrl+V的方式對(duì)Acegi進(jìn)行介紹,沒有意義,隨便google就一大堆
我們想主要在這樣的系統(tǒng)中需要的安全策略都有哪些?
1.用戶的登陸
2.防止多個(gè)用戶登陸一個(gè)帳號(hào)
3.用戶的注銷
4.防止非法用戶的訪問
我這個(gè)程序所涉及到的只有這些,下面就進(jìn)行說明:
在web.xml的聲明:
<!-- Acegi安全控制 Filter 配置 -->
??? <filter>
??????? <filter-name>securityFilter</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>securityFilter</filter-name>
??????? <url-pattern>/*</url-pattern>
??? </filter-mapping>
Acegi通過實(shí)現(xiàn)了Filter接口的FilterToBeanProxy提供一種特殊的使用Servlet Filter的方式,它委托Spring中的Bean -- FilterChainProxy來完成過濾功能,這樣就簡(jiǎn)化了web.xml的配置,并且利用Spring IOC的優(yōu)勢(shì)。FilterChainProxy包含了處理認(rèn)證過程的filter列表,每個(gè)filter都有各自的功能。
<!-- ======================== FILTER CHAIN ======================= -->
?<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
??<property name="filterInvocationDefinitionSource">
???<value>
????CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
????PATTERN_TYPE_APACHE_ANT
????
????/**=httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,
?????????securityContextHolderAwareRequestFilter,exceptionTranslationFilter,filterInvocationInterceptor
???</value>
??</property>
?</bean>
大體上先介紹一下:
httpSessionContextIntegrationFilter:每次request前 HttpSessionContextIntegrationFilter從Session中獲取Authentication對(duì)象,在request完后, 又把Authentication對(duì)象保存到Session中供下次request使用,此filter必須其他Acegi filter前使用,使之能跨越多個(gè)請(qǐng)求。
logoutFilter:用戶的注銷
authenticationProcessingFilter:處理登陸請(qǐng)求
exceptionTranslationFilter:異常轉(zhuǎn)換過濾器
filterInvocationInterceptor:在訪問前進(jìn)行權(quán)限檢查
這些就猶如在web.xml聲明一系列的過濾器,不過當(dāng)把他們都聲明在spring中就可以享受Spring給我們帶來的方便了。
下面就是對(duì)這些過濾器的具體聲明:
只對(duì)有用的地方進(jìn)行聲明,別的地方幾乎都是默許的
<!-- ======================== FILTER ======================= -->
?<bean id="httpSessionContextIntegrationFilter"
??class="org.acegisecurity.context.HttpSessionContextIntegrationFilter" />
?<bean id="logoutFilter" class="org.acegisecurity.ui.logout.LogoutFilter">
??<constructor-arg value="/index.htm" />???????????? 離開后所轉(zhuǎn)向的位置
??<constructor-arg>
??????????? <list>
??????????????? <bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler"/>
??????????? </list>
??????? </constructor-arg>
??<property name="filterProcessesUrl" value="/logout.htm" />??????? 定義用戶注銷的地址,
?</bean>
下面的這個(gè)過濾器,我們根據(jù)自己的需求有了自己的實(shí)現(xiàn):
?<bean id="authenticationProcessingFilter" class="subject.web.filter.UserAuthenticationProcessingFilter">
??<property name="authenticationManager" ref="authenticationManager"/>??下面會(huì)介紹的用來起到認(rèn)證管理的作用
??<property name="authenticationFailureUrl" value="/login.htm?error=wrong"/>? 登陸失敗的地址
??<property name="defaultTargetUrl" value="/login.htm"/>?????? 登陸成功的地址
??<property name="filterProcessesUrl" value="/j_security_check"/>????? 登陸請(qǐng)求的地址
??<property name="userManager" ref="userManager"/>??????? 自己添加的屬性,這樣就可以訪問到我們的業(yè)務(wù)邏輯
??<property name="exceptionMappings">?? 出現(xiàn)異常所對(duì)應(yīng)的地址
??????????? <value>
??????????????? org.acegisecurity.AuthenticationException=/login.htm?error=fail???? 登陸失敗??????????????? org.acegisecurity.concurrent.ConcurrentLoginException=/login.htm?error=too??????? 已登陸了
??????????? </value>
??????? </property>
?</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="/login.htm?error=please"/>//如果用戶沒登陸就想訪問,先到這里登陸吧
????<property name="forceHttps" value="false"/>
???</bean>
??</property>
?</bean>
?
?<bean id="filterInvocationInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
??<property name="authenticationManager" ref="authenticationManager"/>???????認(rèn)證服務(wù)
??<property name="accessDecisionManager">
???<bean class="org.acegisecurity.vote.AffirmativeBased">
????<property name="allowIfAllAbstainDecisions" value="false"/>
????<property name="decisionVoters">
?????<list>
??????<bean class="org.acegisecurity.vote.RoleVoter">
??????????????????? <property name="rolePrefix" value=""/>???????? //這里定義數(shù)據(jù)庫中存放的角色和我們?cè)谶@里聲明的角色間是否需要加個(gè)前綴?我沒加
??????????????? </bean>
?????</list>
????</property>
???</bean>
??</property>
??<property name="objectDefinitionSource">
??????????? <value>
??????????????? PATTERN_TYPE_APACHE_ANT
???????????????
??????????????? /admin.htm*=a???????? 這里就是數(shù)據(jù)庫中對(duì)應(yīng)的tyep a
??????????????? /student*=s?????????? 由于沒有前綴和數(shù)據(jù)庫里一樣
??????????????? /teacher*=t
??????????? </value>
??????? </property>
?</bean>
?
?<bean id="loggerListener"
????????? class="org.acegisecurity.event.authentication.LoggerListener"/>?????? 記錄事件
下面就要說明我們的認(rèn)證服務(wù)了,其起到的關(guān)鍵作用就是用來保證用戶登陸身份的驗(yàn)證:
它將驗(yàn)證的功能委托給多個(gè)Provider,并通過遍歷Providers, 以保證獲取不同來源的身份認(rèn)證,若某個(gè)Provider能成功確認(rèn)當(dāng)前用戶的身份,authenticate()方法會(huì)返回一個(gè)完整的包含用戶授權(quán)信息的Authentication對(duì)象,否則會(huì)拋出一個(gè)AuthenticationException。
先聲明一個(gè)管理器吧,在上面的過濾器中都已經(jīng)用到過了
<!-- ======================== AUTHENTICATION ======================= -->
?<bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
??<property name="providers">
???<list>
????<ref local="daoAuthenticationProvider" />???我僅僅用到 從數(shù)據(jù)庫中讀取用戶信息驗(yàn)證身份
???</list>
??</property>
??<property name="sessionController">
???<bean id="concurrentSessionController"
????class="org.acegisecurity.concurrent.ConcurrentSessionControllerImpl">
????<property name="maximumSessions">
?????<value>1</value>每個(gè)用戶同時(shí)登陸一位
????</property>
????<property name="sessionRegistry">
?????<bean id="sessionRegistry" class="org.acegisecurity.concurrent.SessionRegistryImpl" />
????</property>
????<property name="exceptionIfMaximumExceeded" value="true" />
???</bean>
??</property>
?</bean>
?來實(shí)現(xiàn)唯一的一個(gè)Provider,從數(shù)據(jù)庫驗(yàn)證身份
?<bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
??<property name="userDetailsService">
???<bean id="jdbcDaoImpl"
??????????? class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl">
????????? <property name="dataSource" ref="dataSource"/>
????????? <property name="usersByUsernameQuery">
????????????? <value>
????????????????? select user,password,about from user where user = ???????? 查找用戶的查詢語句,只需要把你數(shù)據(jù)庫中的用戶和密碼以及enable相對(duì)應(yīng)上就行
????????????? </value>
????????? </property>
????????? <property name="authoritiesByUsernameQuery">
????????????? <value>
????????????????? select user,type from user where user = ??????????? 這里就是把用戶和權(quán)限對(duì)應(yīng)上,在appfuse中用的兩個(gè)表,我都放一個(gè)表里了,所以就用這一個(gè)就行問題的關(guān)鍵是要讓它能找到兩個(gè)字段,構(gòu)成一個(gè)對(duì)象
????????????? </value>
????????? </property>
????? </bean>
??</property>
??<property name="userCache"> 緩存都這么寫:
???<bean class="org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache">
????<property name="cache">
?????<bean class="org.springframework.cache.ehcache.EhCacheFactoryBean">
??????<property name="cacheManager">
???????<bean class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/>
??????</property>
??????<property name="cacheName" value="userCache"/>
?????</bean>
????</property>
???</bean>
??</property>
?</bean>
==============
對(duì)于上面登陸請(qǐng)求的處理器我借鑒了springSide,實(shí)現(xiàn)的方法如下:
package subject.web.filter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.acegisecurity.Authentication;
import org.acegisecurity.context.SecurityContext;
import org.acegisecurity.context.SecurityContextHolder;
import org.acegisecurity.ui.webapp.AuthenticationProcessingFilter;
import org.acegisecurity.userdetails.UserDetails;
import subject.Constants;
import subject.model.User;
import subject.service.UserManager;
public class UserAuthenticationProcessingFilter extends
??AuthenticationProcessingFilter
{
?private UserManager userManager;
?public void setUserManager( UserManager userManager )
?{
??this.userManager = userManager;
?}
?protected boolean requiresAuthentication( HttpServletRequest request ,
???HttpServletResponse response )
?{
??boolean requiresAuth = super.requiresAuthentication( request, response );
??HttpSession httpSession = null;
??try
??{
???httpSession = request.getSession( false );
??}
??catch ( IllegalStateException ignored )
??{
??}
??if ( httpSession != null )
??{
???if ( httpSession.getAttribute( Constants.USER ) == null )
???{
????if ( !requiresAuth )
????{
?????SecurityContext sc = SecurityContextHolder.getContext();
?????Authentication auth = sc.getAuthentication();
?????if ( auth != null
???????&& auth.getPrincipal() instanceof UserDetails )
?????{
??????UserDetails ud = (UserDetails) auth.getPrincipal();//上面聲明的sql無非就是要包裝成這個(gè)對(duì)象
??????User user = userManager.getUser( ud.getUsername() );從業(yè)務(wù)邏輯里找到用戶,放到session里
??????httpSession.setAttribute( Constants.USER, user );
?????}
????}
???}
??}
??return requiresAuth;
?}
}
在看看我的login.htm在登陸成功時(shí)是怎么工作的吧?
public class UserAction extends BaseAction
{
?private UserManager mgr;
?public void setUserManager( UserManager mgr )
?{
??this.mgr = mgr;
?}
?public ActionForward login( ActionMapping mapping , ActionForm form ,
???HttpServletRequest request , HttpServletResponse response )
???throws Exception
?{
??User user = (User) getSessionObject( request, Constants.USER );
??ActionMessages msg = new ActionMessages();
??if ( user != null )
??{
???return new ActionForward(? user.getType() + ".htm", true );成功就去type.htm
??}
??else
??{
???String error = getParameter( request, Constants.ERROR );
???if ( error != null )對(duì)于不同的錯(cuò)誤,都加以提示
???{
????if ( error.equalsIgnoreCase( "wrong" ) )
?????msg.add( "msg", new ActionMessage( "fail.login.wrong" ) );
????else if ( error.equalsIgnoreCase( "too" ) )
?????msg.add( "msg", new ActionMessage( "fail.login.too" ) );
????else if ( error.equalsIgnoreCase( "fail" ) )
?????msg.add( "msg", new ActionMessage( "fail.login.fail" ) );
????else
?????msg.add( "msg", new ActionMessage( "fail.login.please" ) );
???}
???else
????msg.add( "msg", new ActionMessage( "fail.login.please" ) );
??}
??saveErrors( request, msg );
??return mapping.findForward( "fail" );
?}
}
當(dāng)然,Acegi需要介紹的東西太多了,我只把我這次認(rèn)為有必要解釋的東西寫在了上面讓大家來參考,作為能google到的東西,比如對(duì)于認(rèn)證的方式還有很多,我就沒有詳細(xì)的介紹,在學(xué)習(xí)Acegi過程中,把它自帶的例子弄清楚很關(guān)鍵,希望大家一起學(xué)習(xí)一起共勉!