Spring+Hibernate+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過程中,把它自帶的例子弄清楚很關鍵,希望大家一起學習一起共勉!