系统,三种角色:教师,学生,管理员,我想让他们的登陆都在一个界面下自动识别,而无需进行身份选择,登陆后,他们将分别到各自的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过程中,把它自带的例子弄清楚很关键,希望大家一起学习一起共勉!