【SSI开发总结.4】Spring中使用Acegi安全框架

Acegi认证授权主要基于两大技术,一是Filter机制,二是AOP的拦截 机制。通过FilterSecurityInterceptor很好地实现了对URI的保护,通过MethodSecurityInterceptor实 现了对Service的方法的拦截保护,通过ACL 实现了对prototype类型的Object进行过滤和保护。

HttpSessionContextIntegrationFilter 存储SecurityContext in HttpSession
ChannelProcessingFilter 重定向到另一种协议,如http到https

ConcurrentSessionFilter 因为不使用任何SecurityContextHolder的功能,但是需要更新SessionRegistry来表示当前的发送请求的 principal,通过在web.xml中注册Listener监听Session事件,并发布相关消息,然后由SessionRegistry获得消 息以判断当前用户的Session数量。

AuthenticationProcessingFilter 普通认证机制(大多数用这个)

CasProcessingFilter CAS认证机制

BasicProcessingFilter Http协议的Basic认证机制

HttpRequestIntegrationFilter Authentication 从容器的HttpServletRequest.getUserPrincipal()获得

JbossIntegrationFilter 与Jboss相关。

SecurityContextHolderAwareRequestFilter 与servlet容器结合使用。

RememberMeProcessingFilter 基于Cookies方式进行认证。

AnonymousProcessingFilter 匿名认证。

ExceptionTranslationFilter 捕获所有的Acegi Security 异常,这样要么返回一个HTTP错误响应或者加载一个对应的AuthenticationEntryPoint

AuthenticationEntryPoint 认证入口

Acegi认证授权流程
1、FilterToBeanProxy 负责代理请求给FilterChainProxy

2、FilterChainProxy 方便的将多个Filter串联起来,如上面基本概念中提到的各种Filter,当然如果对URI进行授权保护,也可以包含FilterSecurityInterceptor。注意各Filter的顺序。

3、AbstractSecurityInterceptor 调度中心。负责调用各模块完成相应功能。
FilterSecurityInterceptor 对URI进行拦截保护
AspectJSecurityInterceptor 对方法进行拦截保护
MethodSecurityInterceptor 对方法进行拦截保护

4、AuthenticationManager 用户认证
-> AuthenticationProvider 实际进行用户认证的地方(多个)。
-> UserDetailsService 返回带有GrantedAuthority的UserDetail或者抛出异常。

5、AccessDecisionManager(UnanimousBased/AffirmativeBased/ConsensusBased) 授权
-> AccessDecisionVoter(RoleVoter/BaseAclEntryVoter) 实际投票的Voter(多个).

6、RunAsManager 变更GrantedAuthority

7、AfterInvocationManager 变更返回的对象
-> BaseInvocationProvider 实际完成返回对象变更的地方(多个)。

在我的项目中,用ACEGI实现了会员系统的权限控制操作

1.配置web.xml

/****** /web-inf/web.xml ******/

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>game_proj</display-name>
<!-- 中文语言支持过滤器 -->
<filter>
<filter-name>Set Character Encoding</filter-name>
<filter-class>com.popoann.SetCharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>GBK</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>Set Character Encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<!-- Acegi过滤器 -->
<filter-name>acegiFilterChain</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>
<!-- Acegi过滤器URL映射 -->
<filter-mapping>
<filter-name>acegiFilterChain</filter-name>
<url-pattern>*.htm</url-pattern>
</filter-mapping>

<filter-mapping>
<filter-name>acegiFilterChain</filter-name>
<url-pattern>*.jsp</url-pattern>
</filter-mapping>

<filter-mapping>
<filter-name>acegiFilterChain</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>

<!-- ActionContextCleanUp过滤器 -->
<filter>
<filter-name>struts-cleanup</filter-name>
<filter-class>org.apache.struts2.dispatcher.ActionContextCleanUp</filter-class>
</filter>
<filter-mapping>
<filter-name>struts-cleanup</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- SiteMesh的核心过滤器 -->
<filter>
<filter-name>sitemesh</filter-name>
<filter-class>com.opensymphony.module.sitemesh.filter.PageFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>sitemesh</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- Struts核心过滤器 -->
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
<init-param>
<param-name>struts.action.extension</param-name>
<param-value>htm</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<!-- 应用程序启动时,加载SpringIoC容器 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/beans-*.xml
</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 欢迎页面 -->
<welcome-file-list>
<welcome-file>index.htm</welcome-file>
</welcome-file-list>

</web-app>

2.配置spring bean

/****** /web-inf/beans-myspace_security.xml ******/

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"
"http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
<!-- 1.配制认证管理器(AuthenticationManager) -->
<bean id="myspace_authenticationManager"
class="org.acegisecurity.providers.ProviderManager">
<property name="providers">
<list>
<ref bean="myspace_daoAuthenticationProvider"/>
<ref bean="myspace_rememberMeAuthenticationProvider"/>
</list>
</property>
</bean>

<!-- 表示通过DAO接口进行数据库验证 -->

<!-- 指定userDetailsService,表示用户列表由这个类得到,后面编程实现这个自定义用户列表类 -->

<!-- 指定passwordEncoder,表示在验证之前,对表单中的密码先进行指定编码方式的加密,这里为MD5加密策略 -->
<bean id="myspace_daoAuthenticationProvider"
class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="myspace_userDetailsBo"/>
<property name="passwordEncoder" ref="myspace_passwordEncoder"/>
</bean>
<bean id="myspace_passwordEncoder"
class="org.acegisecurity.providers.encoding.Md5PasswordEncoder"/>

<!-- 表示通过COOKIES进行验证 -->
<bean id="myspace_rememberMeAuthenticationProvider"
class="org.acegisecurity.providers.rememberme.RememberMeAuthenticationProvider">
<property name="key" value="myspace_remember_me"/>
</bean>


<!-- 2.配制决策管理器(AccessDecisionManager) -->
<bean id="myspace_accessDecisionManager"
class="org.acegisecurity.vote.AffirmativeBased">
<property name="decisionVoters">
<list>
<bean class="org.acegisecurity.vote.RoleVoter"/>
</list>
</property>
<property name="allowIfAllAbstainDecisions" value="false"/>
</bean>
<!-- 3.配制拦截器链(FilterChain) -->
<bean id="myspace_filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value>


PATTERN_TYPE_APACHE_ANT
/myspace/*.htm*=myspace_httpSessionContextIntegrationFilter,myspace_logoutFilter,myspace_authenticationProcessingFilter,myspace_rememberMeFilter,myspace_exceptionFilter,myspace_securityInterceptor
/**=myspace_httpSessionContextIntegrationFilter,myspace_logoutFilter,myspace_authenticationProcessingFilter,myspace_rememberMeFilter,myspace_exceptionFilter,myspace_securityInterceptor

</value>
</property>
</bean>
<!-- (1) Session会话验证拦截器-->
<bean id="myspace_httpSessionContextIntegrationFilter"
class="org.acegisecurity.context.HttpSessionContextIntegrationFilter"/>
<!-- (2) 注销功能拦截器-->
<bean id="myspace_logoutFilter"
class="org.acegisecurity.ui.logout.LogoutFilter">
<!-- 注销后默认的跳转页面 -->
<constructor-arg value="/login.htm"/>
<constructor-arg>
<list>
<ref bean="myspace_rememberMeServices"/>
<bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler"/>
</list>
</constructor-arg>
<!-- 用户注销URL -->
<property name="filterProcessesUrl" value="/j_logout.do"/>
</bean>
<!-- (3) -->
<bean id="myspace_authenticationProcessingFilter"
class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
<property name="authenticationManager" ref="myspace_authenticationManager"/>
<!-- 登录失败的页面,通常包含出错信息的登录页面 -->
<property name="authenticationFailureUrl" value="/login!error.htm"/>
<!-- 登录后默认跳转页面 -->
<property name="defaultTargetUrl" value="/myspace/hellouser.htm"/>
<!-- 用户登录URL -->
<property name="filterProcessesUrl" value="/j_login.do"/>
<property name="rememberMeServices" ref="myspace_rememberMeServices"/>
</bean>
<!-- (4) cookies验证拦截器-->
<bean id="myspace_rememberMeFilter"
class="org.acegisecurity.ui.rememberme.RememberMeProcessingFilter">
<property name="authenticationManager" ref="myspace_authenticationManager"/>
<property name="rememberMeServices" ref="myspace_rememberMeServices"/>
</bean>
<bean id="myspace_rememberMeServices"
class="org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices">
<property name="userDetailsService" ref="myspace_userDetailsBo"/>
<property name="parameter" value="myspace_remember_me"/>
<property name="key" value="myspace_remember_me"/>
<property name="tokenValiditySeconds" value="31536000"/>
</bean>
<!-- (5) -->
<bean id="myspace_exceptionFilter"
class="org.acegisecurity.ui.ExceptionTranslationFilter">
<!-- 出现AuthenticationException时的登陆入口 -->
<property name="authenticationEntryPoint">
<bean class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
<property name="loginFormUrl" value="/login.htm"/>
<property name="forceHttps" value="false"/>
</bean>
</property>

<!-- 指定错误页,当前角色不能访问本资源时,定位到错误页 -->
<!-- 出现AccessDeniedException时的handler -->
<property name="accessDeniedHandler">
<bean class="org.acegisecurity.ui.AccessDeniedHandlerImpl">
<property name="errorPage" value="/error.jsp" />
</bean>
</property>
</bean>
<!-- (6) 安全拦截器,整合认证和决策管理器,并定义各种目录结构对应的角色,可用ANT表达式-->
<bean id="myspace_securityInterceptor"
class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
<property name="authenticationManager" ref="myspace_authenticationManager"/>
<property name="accessDecisionManager" ref="myspace_accessDecisionManager"/>
<property name="objectDefinitionSource">
<value>
PATTERN_TYPE_APACHE_ANT
/myspace/sales_*.htm*=ROLE_SALES_USER
/myspace/provide_*.htm*=ROLE_PROVIDE_USER

</value>
</property>
</bean>
</beans>

一定要注意Filter的配置顺序,还有更多Filter,不过以上足够了

3.编写UserDetails类

/****** myspace_userDetailsBo.java ******/

package bo;

import org.acegisecurity.userdetails.UserDetailsService;
import org.acegisecurity.userdetails.UserDetails;
import org.acegisecurity.userdetails.User;
import org.acegisecurity.userdetails.UsernameNotFoundException;
import org.springframework.dao.DataAccessException;
import org.acegisecurity.GrantedAuthorityImpl;
import org.acegisecurity.GrantedAuthority;
import bo.UserBo;
import java.util.*;

public class UserDetailsBo implements UserDetailsService {

//定义两个角色
private final GrantedAuthority ROLE_SALES_USER=new GrantedAuthorityImpl("ROLE_SALES_USER");
private final GrantedAuthority ROLE_PROVIDE_USER=new GrantedAuthorityImpl("ROLE_PROVIDE_USER");


private UserBo bo;
private model.User user;
private List< model.User> myusers;
private Map<String,String> users=new HashMap<String,String>();;
private GrantedAuthority[] authorities;
//注入业务对象的同时,把得到的用户列表依次拷贝的一个HashMap对象中
public void setBo(UserBo bo) {
this.bo = bo;
myusers=bo.getUserList();
users.put("test", "test");
for(model.User user:myusers){
users.put(user.getUsername().trim(), user.getPassword().trim());
System.out.println(user.getUsername().trim()+"=="+user.getPassword().trim());
}
}
public UserDetailsBo(){

}
/* 必须实现这个接口方法,改方法表示在验证之前,该用户是否存在,如果不存在,当然就没有验证的必要了,如果存在,之前通过表单得到的用户名,密码就要和本类中定义的Hashmap中存在的用户密码名值对做对比,然后根据spring bean配置实现验证 */
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {
// TODO Auto-generated method stub
System.out.println(username);
String password=users.get(username);
if(password==null)
throw new UsernameNotFoundException("No such user");
System.out.println(password);
if(bo.getUser(username).getType()==1)
authorities=new GrantedAuthority[]{ROLE_SALES_USER};
else
authorities=new GrantedAuthority[]{ROLE_PROVIDE_USER};
return new User(username,password,true,true,true,true,authorities);
}

}

4. 根据配置,制作登录表单

<table width="167" height="80" border="0" cellpadding="0" cellspacing="0">
<form onSubmit="return doValidate('_login')" id="_login" name="_login" method="post" action="j_login.do">
<tr class="style3">
<td width="60" align="right" class="style3"><span class="STYLE9 style3">用户名:</span></td>
<td width="107"><input name="j_username " type="text" class="STYLE9 " id="j_username" size="15" /></td>
</tr>
<tr class="style3">
<td align="right" class="style3"><span class="STYLE9 style3">密&nbsp;&nbsp;码:</span></td>
<td><input name="j_password " type="password" class="STYLE9 " id="j_password" size="15" /></td>
</tr>
<tr class="style3">
<td colspan="2" align="center"><table width="100%" height="27" border="0" cellpadding="0" cellspacing="0">
<tr>
<td align="center" class="style2"><input style=" background-image:url(images/btnbg.gif); width:67px; height:22px" type="submit" name="Submit" value="登录" /><input type="checkbox" name="myspace_remember_me "/>
记住用户</td>
</tr>
</table></td>
</tr>
</form>
</table>

注意高亮的三个地方,用户名和密码的字段必须是j_username,j_password,myspace_remember_me这个名称显然 要和spring bean配置中,myspace_rememberMeAuthenticationProvider这个bean的key属性对应

到此,安全验证完成。

你可能感兴趣的:(spring)