系統,三種角色:教師,學生,管理員,我想讓他們的登陸都在一個界面下自動識別,而無需進行身份選擇,登陸后,他們將分別到各自的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; ??
- ??
- ????
- ???
- ?public?abstract?String?getType();? ??
- } ??
子類的實現:
======================
代碼
- package?subject.model; ??
- ??
- import?subject.Constants; ??
- ??
- public?class?Teacher?extends?User ??
- { ??
- ?private?String?level;???????????
- ??
- ??
- ??
- ?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;???????????????
- ??
- ??
- ? ??
- ?public?String?getType() ??
- ?{ ??
- ??return?Constants.STUDENT; ??
- ?} ??
- } ??
=================
代碼
- package?subject.model; ??
- ??
- import?subject.Constants; ??
- ??
- public?class?Admin?extends?User ??
- { ??
- ?private?String?grade;?????????????
- ??
- ??
- ?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的配置只介紹不說明:
代碼
- ??
- ?<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>??
- ??
- ???
- ?<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>??
- ??
- ??
- <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>??
- ??
- ??
- ?<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的聲明:
代碼
- ??
- ????<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都有各自的功能。
代碼
- ??
- ?<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給我們帶來的方便了。
下面就是對這些過濾器的具體聲明:
只對有用的地方進行聲明,別的地方幾乎都是默許的
代碼
- ??
- ?<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>??
- ??
下面的這個過濾器,我們根據自己的需求有了自己的實現:
代碼
==============
對于上面登陸請求的處理器我借鑒了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();??
- ??????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過程中,把它自帶的例子弄清楚很關鍵,希望大家一起學習一起共勉!