到現在我也沒有弄明白Acegi里面很多的功能,剛剛開始學的時候我就已經被它那繁瑣的配置震懾住了,不過當我動起手來一步步實現的時候,才發現其實它遠沒有那么難,當然隨著學習的深入,會漸漸再發現這一點吧,現在就讓我們初學者一切體驗Acegi的功能吧!
還以我傳統的例子為例:
畢業設計選題系統,三種角色:教師,學生,管理員,我想讓他們的登陸都在一個界面下自動識別,而無需進行身份選擇,登陸后,他們將分別到各自的admin.jsp,stu.jsp,teacher.jsp
在數據庫中的表結構如下(很多屬性略):
id--- user---password--type---about
type是用來存儲用戶的類別,分別有a,t,s分別對應三種角色
about對應的是acegi里所需要的enable,用戶是否可用
在model里,我們采用了繼承關系:
父類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(); //這個是用來反映用戶角色的關鍵函數,在子類實現,從而實現多態
}
子類的實現:
======================
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;???????? //學生的班級
?private String sn;???????????? //學生的學號
//set and get method
?
?public String getType()
?{
??return Constants.STUDENT;
?}
}
=================
package subject.model;
import subject.Constants;
public class Admin extends User
{
?private String grade;?????????? //管理員的級別
//set and get method
?public String getType()
?{
??return Constants.ADMIN;
?}
}
對于三者所共有的屬性在數據庫里,都存在一個字段,而依據不同的角色擁有不同的含義,學生的班級則存放在了about里,只要學生有班級,他就able,否則就enable了!而管理員和教師則默認為1!
這種是屬于一個繼承樹存放在一個表的情況,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>
=============================================
上面的這些都是模型的基礎,下面再講怎么樣配合Spring和Acegi實現系統的安全與登陸
在Spring中Hibernate的配置只介紹不說明:
<!-- 定義DBCP數據源 -->
?<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對象 -->
<bean id="userDao" class="subject.dao.hibernate.UserDaoImpl">
??<property name="sessionFactory" ref="sessionFactory" />
?</bean>
<!-- 業務邏輯 -->
?<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>
==================
上面具體的不用了解,無非就是調用和數據庫的操作,
下面就要對Acegi進行聲明了:
我不用Ctrl+c和Ctrl+V的方式對Acegi進行介紹,沒有意義,隨便google就一大堆
我們想主要在這樣的系統中需要的安全策略都有哪些?
1.用戶的登陸
2.防止多個用戶登陸一個帳號
3.用戶的注銷
4.防止非法用戶的訪問
我這個程序所涉及到的只有這些,下面就進行說明:
在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通過實現了Filter接口的FilterToBeanProxy提供一種特殊的使用Servlet Filter的方式,它委托Spring中的Bean -- FilterChainProxy來完成過濾功能,這樣就簡化了web.xml的配置,并且利用Spring IOC的優勢。FilterChainProxy包含了處理認證過程的filter列表,每個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對象,在request完后, 又把Authentication對象保存到Session中供下次request使用,此filter必須其他Acegi filter前使用,使之能跨越多個請求。
logoutFilter:用戶的注銷
authenticationProcessingFilter:處理登陸請求
exceptionTranslationFilter:異常轉換過濾器
filterInvocationInterceptor:在訪問前進行權限檢查
這些就猶如在web.xml聲明一系列的過濾器,不過當把他們都聲明在spring中就可以享受Spring給我們帶來的方便了。
下面就是對這些過濾器的具體聲明:
只對有用的地方進行聲明,別的地方幾乎都是默許的
<!-- ======================== FILTER ======================= -->
?<bean id="httpSessionContextIntegrationFilter"
??class="org.acegisecurity.context.HttpSessionContextIntegrationFilter" />
?<bean id="logoutFilter" class="org.acegisecurity.ui.logout.LogoutFilter">
??<constructor-arg value="/index.htm" />???????????? 離開后所轉向的位置
??<constructor-arg>
??????????? <list>
??????????????? <bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler"/>
??????????? </list>
??????? </constructor-arg>
??<property name="filterProcessesUrl" value="/logout.htm" />??????? 定義用戶注銷的地址,
?</bean>
下面的這個過濾器,我們根據自己的需求有了自己的實現:
?<bean id="authenticationProcessingFilter" class="subject.web.filter.UserAuthenticationProcessingFilter">
??<property name="authenticationManager" ref="authenticationManager"/>??下面會介紹的用來起到認證管理的作用
??<property name="authenticationFailureUrl" value="/login.htm?error=wrong"/>? 登陸失敗的地址
??<property name="defaultTargetUrl" value="/login.htm"/>?????? 登陸成功的地址
??<property name="filterProcessesUrl" value="/j_security_check"/>????? 登陸請求的地址
??<property name="userManager" ref="userManager"/>??????? 自己添加的屬性,這樣就可以訪問到我們的業務邏輯
??<property name="exceptionMappings">?? 出現異常所對應的地址
??????????? <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"/>???????認證服務
??<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=""/>???????? //這里定義數據庫中存放的角色和我們在這里聲明的角色間是否需要加個前綴?我沒加
??????????????? </bean>
?????</list>
????</property>
???</bean>
??</property>
??<property name="objectDefinitionSource">
??????????? <value>
??????????????? PATTERN_TYPE_APACHE_ANT
???????????????
??????????????? /admin.htm*=a???????? 這里就是數據庫中對應的tyep a
??????????????? /student*=s?????????? 由于沒有前綴和數據庫里一樣
??????????????? /teacher*=t
??????????? </value>
??????? </property>
?</bean>
?
?<bean id="loggerListener"
????????? class="org.acegisecurity.event.authentication.LoggerListener"/>?????? 記錄事件
下面就要說明我們的認證服務了,其起到的關鍵作用就是用來保證用戶登陸身份的驗證:
它將驗證的功能委托給多個Provider,并通過遍歷Providers, 以保證獲取不同來源的身份認證,若某個Provider能成功確認當前用戶的身份,authenticate()方法會返回一個完整的包含用戶授權信息的Authentication對象,否則會拋出一個AuthenticationException。
先聲明一個管理器吧,在上面的過濾器中都已經用到過了
<!-- ======================== AUTHENTICATION ======================= -->
?<bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
??<property name="providers">
???<list>
????<ref local="daoAuthenticationProvider" />???我僅僅用到 從數據庫中讀取用戶信息驗證身份
???</list>
??</property>
??<property name="sessionController">
???<bean id="concurrentSessionController"
????class="org.acegisecurity.concurrent.ConcurrentSessionControllerImpl">
????<property name="maximumSessions">
?????<value>1</value>每個用戶同時登陸一位
????</property>
????<property name="sessionRegistry">
?????<bean id="sessionRegistry" class="org.acegisecurity.concurrent.SessionRegistryImpl" />
????</property>
????<property name="exceptionIfMaximumExceeded" value="true" />
???</bean>
??</property>
?</bean>
?來實現唯一的一個Provider,從數據庫驗證身份
?<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 = ???????? 查找用戶的查詢語句,只需要把你數據庫中的用戶和密碼以及enable相對應上就行
????????????? </value>
????????? </property>
????????? <property name="authoritiesByUsernameQuery">
????????????? <value>
????????????????? select user,type from user where user = ??????????? 這里就是把用戶和權限對應上,在appfuse中用的兩個表,我都放一個表里了,所以就用這一個就行問題的關鍵是要讓它能找到兩個字段,構成一個對象
????????????? </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>
==============
對于上面登陸請求的處理器我借鑒了springSide,實現的方法如下:
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無非就是要包裝成這個對象
??????User user = userManager.getUser( ud.getUsername() );從業務邏輯里找到用戶,放到session里
??????httpSession.setAttribute( Constants.USER, user );
?????}
????}
???}
??}
??return requiresAuth;
?}
}
在看看我的login.htm在登陸成功時是怎么工作的吧?
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 )對于不同的錯誤,都加以提示
???{
????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" );
?}
}
當然,Acegi需要介紹的東西太多了,我只把我這次認為有必要解釋的東西寫在了上面讓大家來參考,作為能google到的東西,比如對于認證的方式還有很多,我就沒有詳細的介紹,在學習Acegi過程中,把它自帶的例子弄清楚很關鍵,希望大家一起學習一起共勉!