Spring Security Acegi 学习之路三 (转)
身份认证管理
?
?
使用Acegi保护应用程序的第一步是根据用户提供的认证信息进行身份认证,以确定用户的身份获取对应的权限信息准备好Authentication。通过认证的Authentication拥有权限信息,它是Acegi进行后续安全对象访问安全控制的依据。
?
基于内存存储用户信息的身份认证
?
applicationContext-acegi-plugin.xml
?
<bean id="filterChainProxy" authenticationProcessingFilter " value="/j_acegi_security_check"/> <property name="defaultTargetUrl" value="/main.jsp"/> <property name="authenticationFailureUrl" value="/index.jsp?login_error=1"/></bean>?
添加登录页面index.jsp:
?
<%@ taglib prefix=”c” uri=http://java.sun.com/jsp/jstl/core%”><form name="form1" method="post" action="<c:url value="/j_acegi_security_check"/>">用户名:<input type="text" name="j_username"/><br/>密 码:<input type="password" name="j_password"/><br/> <input type="submit" value="登录"/></form>?
applicationContext-acegi-plugin.xml : 认证管理器的配置
?
<bean id="authenticationProcessingFilter"value="/j_acegi_security_check"/> <!--认证成功后转向的URL --> <property name="defaultTargetUrl" value="/main.jsp"/> <!--认证失败后转向的URL --> <property name="authenticationFailureUrl" value="/index.jsp?login_error=1" /> <!--注入认证管理器 --> <property name="authenticationManager" ref="authenticationManager"/></bean><bean id="authenticationManager"/> </list> </property></bean><bean id="daoAuthenticationProvider"ref="userDetailsService" /></bean><bean id="userDetailsService"style="">?? Acegi提供了不同的AuthenticationProvider的实现,如:?DaoAuthenticationProvider 从Dao类负责读取用户信息验证身份?AnonymousAuthenticationProvider 匿名用户身份认证?RememberMeAuthenticationProvider 已存cookie中的用户信息身份认证?AuthByAdapterProvider 使用容器的适配器验证身份?CasAuthenticationProvider 根据Yale中心认证服务验证身份, 用于实现单点登陆?JaasAuthenticationProvider 从JASS登陆配置中获取用户信息验证身份?RemoteAuthenticationProvider 根据远程服务验证用户身份?RunAsImplAuthenticationProvider 对身份已被管理器替换的用户进行验证?X509AuthenticationProvider 从X509认证中获取用户信息验证身份?TestingAuthenticationProvider 单元测试时使用DaoAuthenticationProvider通过UserDetailsService完成UserDetails的获取工作,根据存储用户信息媒介的不同,Acegi提供了两个UserDetailsService的实现类:
?
InMemoryDaoImpl:该实现类负责从内在中获取用户的信息JdbcDaoImpl:该实现类从数据库中获取用户的信息如果用户数比较多,在Spring中直接进行配置未免不太雅观,这时,可以将用户信息转移到一个属性文件中,并通过userProperties进行加载,则需对userDetailsService稍做修改
?
<bean id="userDetailsService"value="/WEB-INF/users.properties"/></bean></property></bean>??
?基于数据库存储用户信息的认证
?
?
<bean id="userDetailsService"ref="dataSource" /> <property name="usersByUsernameQuery"> <value> <!--根据用户名查询用户的SQL语句--> SELECT username,password,status FROM t_user WHERE username = ? </value> </property> <property name="authoritiesByUsernameQuery"> <value> <!--根据用户名查询用户权限记录的SQL语句--> SELECT u.username,p.priv_name FROM t_user u,t_user_privp WHERE u.user_id =p.user_id AND u.username = ? </value> </property></bean>?
?? ? 应该说JdbcDaoImpl还不是非常实用的UserDetailsService实现类,因为用户对象除包含用户名/密码、是否激活、权限等信息外,还经常需要包含一些诸如email、telephone等到业务相关的信息,所以我们往往需要通过实现UserDetailsService接口提供自己的的实现类来完成这些工作。
?? ? 具体可以参照:Spring Security 2 配置精讲:? http://www.iteye.com/topic/319965
?
?
在获取UserDetails后,DaoAuthenticationProvider要做的工作是比较Authentication 和UserDetails的匹配关系并给出认证成功或失败的认证结果。下面是两个关键接口:
?
org.acegisecurity.providers.encoding.PasswordEncoderorg.acegisecurity.providers.dao.SaltSource?
?? ?PasswordEncoder完成两件工作:
?
?? 对明文的密码(Authentication#getCredentials())进行编码?? 对处于非对称状态(一个是加密的,另一个是明文的)?
?? ?PasswordEncoder进行密码比较时,需要使用到一个SaltSource,它代表一个“加密盐”,对用户提供的密码进行加密时采用的加密盐必须和系统中保存的用户加密密码所采用的加密盐相同。它有两个接口方法:??
String encodePassword(String rawPass, Object salt) //对原始未加密的密码通过一定的算法进行加密运算 Boolean isPasswordValid(String encPass, String rawPass, Object salt) //通过算法判断待认证用户所提供的密码是否有效?
?? ?几种常的PasswordEncoder实现类,密码编码器
Md5PasswordEncoder?? 使用MD5算法加密ShaPasswordEncoder?? 使用SHA算法加密LdapShaPasswordEncoder??? 使用LDAP SHA 和平SSHA算法加密PlaintextPasswordEncoder??? 不加密SaltSource接口公有一个Object gestalt(UserDetails user)方法,它有两个实现类:
org.acegisecurity.providers.dao.salt.ReflectionSaltSource:允许用户在UserDetails中提供一个代表加密盐的属性
org.acegisecurity.providers.dao.salt.SystemWideSaltSource:该实现类不允许不同用户采用各自的加密盐,它采用全局范围统一的加密盐。
?
applicationContext-acegi-plugin.xml :?
<bean id="daoAuthenticationProvider"ref="userDetailsService" /> <property name="passwordEncoder"> <bean value="ccd1010"/> </bean> </property></bean>??
成功登录系统的后置处理
?
?
?? ? 一般的业务系统在用户登录成功后,需要在数据库中记录一条相应的用户登录日志。Acegi会产生一个AuthenticationSuccessEvent事件,该事件是org.springframework.context.ApplicationEvent的子类,所以它是一个Spring容器事件。
?
package com.ccd.service;import org.acegisecurity.Authentication;import org.acegisecurity.event.authentication.AuthenticationSuccessEvent;import org.springframework.context.ApplicationEvent;import org.springframework.context.ApplicationListener;public class LoginSuccessListener implements ApplicationListener{public void onApplicationEvent(ApplicationEvent event){if(event instanceof AuthenticationSuccessEvent){AuthenticationSuccessEvent authEvent = (AuthenticationSuccessEvent) event;Authentication auth = authEvent.getAuthentication();String username = auth.getName();System.out.println(“模拟记录用户[”+username+”]成功登录日志...”);}}}?? ? ??接下来,在Spring容器中声明这个监听器,仅需要一行就可以了:
?
?? ? ? ? <bean class=”com.ccd.service.LoginSuccessListener”/>
?
?
在多个请求之间共享SecurityContext
?
Acegi通过HttpSessionContextIntegrationFilter使SecurityContext在Session级别中共享,当一个请求到达时,它尝试从Session中获取用户关联的SecurityContext并将其放入到SecurityContextHolder中,当请求结束时,HttpSessionContextIntegrationFilter又将SecurityContext转存到HttpSession中。这样,Acegi就通过HttpSessionContextIntegrationFilter将SecurityContext对象在请求级的SecurityContextHolder和Session级的HttpSession中摆渡,从而保证SecurityContext可以在多个请求之间共享。注意,此filter必须于其他Acegi Filter前使用。
?
applicationContext-acegi-plugin.xml :
?
<bean id="filterChainProxy" />?退出系统的后置处理
?
?? ?SecyrityContext保存在HttpSession中,当用户退出系统时必须清除之,否同要等到Session过期后才会被清除,造成额外的内存消耗。Acegi为完成一系列由退出系统引发的操作,专门提供了一个退出过滤器:org.acegisecurity.ui.logout.LogoutFilter。
?
applicationContext-acegi-plugin.xml :
?
<bean id="filterChainProxy" /> </list> </constructor-arg> <!—退出系统后转向的URL --> <constructor-arg value="/index.jsp" /> <!—用于响应退出系统请求的URL--> <property name="filterProcessesUrl" value="/j_acegi_logout" /></bean>?? ? ?配置一个退出系统的操作链接
?
?? ?<Ahref= "<c:url value="/j_acegi_logout"/>">退出系统</A>
?
实施Remember-Me认证
?
