`
webdev2014
  • 浏览: 679847 次
文章分类
社区版块
存档分类
最新评论

acegi security实践教程—logoutFilter应用以及调试

 
阅读更多
一个完整的系统应该都要注销功能。先不着急先代码,首先想象一下,注销后,该出现什么页面呢?
注销后,是不是应该回到登陆页面,让用户重新登录,然后再重新判断过滤权限,一切从头开始。

具体开发步骤:

开发环境:
MyEclispe10.7.1+tomcat6.0.37+acegi1.0.5+spring2.0
项目目录如下: 其中readme主要用来记录本次验证目的

配置文件:
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
		http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
	
	<!-- 通过过滤连形式,acegi提供很多filter,其中过滤器执行也有一定的顺序 ,同事支持正则和ant匹配-->
	
	<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
		<property name="filterInvocationDefinitionSource">
			<value>
				PATTERN_TYPE_APACHE_ANT
				/**=httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
			</value>
		</property>
	</bean>
   	
	<bean id="httpSessionContextIntegrationFilter" 
   		class="org.acegisecurity.context.HttpSessionContextIntegrationFilter" />	
   		
    <!-- 注销功能  因为此类没有默认的构造方法,有两个参数的构造方法 -->
    <bean id="logoutFilter"  class="org.acegisecurity.ui.logout.LogoutFilter">
     <!-- 这是构造器注入方式,第一个参数-->
     <constructor-arg value="/login.jsp"/> 
     <!-- 这是构造器注入方式,第二个参数,其中第二个参数是list参数 -->
      <constructor-arg>  
         <list>  
              <bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler"/>  
         </list>  
      </constructor-arg>  
    </bean>
   		
 <!-- 表单认证处理filter -->  
     <bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">  
        <!-- 认证管理器,然后委托给Provides -->
        <property name="authenticationManager" ref="authenticationManager"/>  
        <!-- 认证失败后转向的url,包含出错信息的的登陆页面 -->
        <property name="authenticationFailureUrl" value="/login.jsp?login_error=1"/>  
        <!-- 登陆成功后转向的url -->
        <property name="defaultTargetUrl" value="/userinfo.jsp"/>  
        <!-- 登陆的url,这个是默认的acegi自带的 -->
        <property name="filterProcessesUrl" value="/j_acegi_security_check"/>  
    </bean> 
	
	<bean id="authenticationManager"
		class="org.acegisecurity.providers.ProviderManager">
		<property name="providers">
			<list>
				<ref local="daoAuthenticationProvider" />
			</list>
		</property>
	</bean>
	
	
    <!-- 从数据库中读取用户信息验证身份 -->
	<bean id="daoAuthenticationProvider"
		class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
		<property name="userDetailsService" ref="inMemDaoImpl" />
	</bean>

    <!-- 基于内存实现方式-->
	<bean id="inMemDaoImpl"
		class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
		<property name="userMap">
			<value>
				test=1,ROLE_USER
				lisi=1,ROLE_SUPERVISOR
				zhangsan=1,ROLE_SUPERVISOR,disabled
			</value>
		</property>
	</bean>
	
	<!-- exception filter -->
   <bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">
     <!-- 尚未登录, 进入非法(未认证不可访问)区域 -->  
        <property name="authenticationEntryPoint">  
            <bean class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">  
                <property name="loginFormUrl" value="/login.jsp"/>  <!--若没登陆,则转向 用户登陆页面 -->
                <property name="forceHttps" value="false"/>  <!-- 是否强制使用https -->
            </bean>  
        </property> 
     <!-- 登录后, 进入非授权区域 --> 
        <property name="accessDeniedHandler">  
            <bean class="org.acegisecurity.ui.AccessDeniedHandlerImpl">  
                <property name="errorPage" value="/accessDenied.jsp"/>  <!-- 进入无权限页面 ,根据需求写相应的信息-->
            </bean>  
        </property>  
    </bean>      
	
   <bean id="filterInvocationInterceptor"
		class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
		<property name="authenticationManager" ref="authenticationManager" />
		<property name="accessDecisionManager" ref="httpRequestAccessDecisionManager" />
		<property name="objectDefinitionSource">
			<value><![CDATA[
				CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
				PATTERN_TYPE_APACHE_ANT
				/userinfo.jsp=ROLE_SUPERVISOR
			]]></value>
		</property>
	</bean>

	<bean id="httpRequestAccessDecisionManager"
		class="org.acegisecurity.vote.AffirmativeBased">
		<property name="decisionVoters">
			<list>
				<bean class="org.acegisecurity.vote.RoleVoter"/>
			</list>
		</property>
	</bean>
</beans>

讲解如下:
那注销过滤器logoutFilter应用如下:
      <!-- 注销功能  因为此类没有默认的构造方法,有两个参数的构造方法 -->
    <bean id="logoutFilter"  class="org.acegisecurity.ui.logout.LogoutFilter">
     <!-- 这是构造器注入方式,第一个参数-->
     <constructor-arg value="/login.jsp"/> 
     <!-- 这是构造器注入方式,第二个参数,其中第二个参数是list参数 -->
      <constructor-arg>  
         <list>  
              <bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler"/>  
         </list>  
      </constructor-arg>  
    </bean>

acegi配置文件中logoutFilter在httpSessionContextIntegrationFilter之后。
spring为啥这么注入呢?这得看logoutFilter具体的类信息【下面截取半截】。
  public class LogoutFilter implements Filter {
    //~ Static fields/initializers =====================================================================================

    private static final Log logger = LogFactory.getLog(LogoutFilter.class);

    //~ Instance fields ================================================================================================

    private String filterProcessesUrl = "/j_acegi_logout";
    private String logoutSuccessUrl;
    private LogoutHandler[] handlers;

    //~ Constructors ===================================================================================================

    public LogoutFilter(String logoutSuccessUrl, LogoutHandler[] handlers) {
        Assert.hasText(logoutSuccessUrl, "LogoutSuccessUrl required");
        Assert.notEmpty(handlers, "LogoutHandlers are required");
        this.logoutSuccessUrl = logoutSuccessUrl;
        this.handlers = handlers;
    }

LogoutFilter有三个属性,还有带有参数的构造器,所以在配置spring注入时,必须注入构造器,因为LogoutFilter没有默认的无参构造函数。若spring配置constructor,当启动tomcat时,会提示没有默认构造函数错误信息。
对于属性使用property来注入,其中filterProcessesUrl类已经有默认值了,可以不配置。至于logoutSuccessUrl,handlers属性,也可以不配置。直接在constructor中配置即可,因为constructor是必须配置的。
logoutSuccessUrl是注销后转向的页面。handlers是个数组,主要是主要处理注销功能,我们配置默认的注销器即可。
配置完成后,根据acegi配置,那我们在表单中添加注销按钮。
页面如下:
那我们在登陆进去的userinfo.jsp中添加注销按钮,配置如下:
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page import="org.acegisecurity.context.SecurityContextHolder"%>  
<%@ page import="org.acegisecurity.userdetails.*"%>  

   <%         Object obj = SecurityContextHolder.getContext().getAuthentication();          
            if (null != obj){  
                Object userDetail = SecurityContextHolder.getContext().getAuthentication().getPrincipal();  
                String username = "";  
                String pwd="";
                if (userDetail instanceof UserDetails) {  
                    username = ((UserDetails) userDetail).getUsername();  
                    pwd = ((UserDetails) userDetail).getPassword();  
                } else {  
                    username = userDetail.toString();  
                }  
                out.print("当前用户:"+username+",密码:"+pwd);  
            } 
        %>  
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>当前用户的具体信息</title>
</head>

<form action="j_acegi_logout" method="post">
  <input type="submit" value="注销系统"/>
</form>
</html>

OK,一切搞定后,大家可以测试一下,注销后转向登陆页面,看看是不是sessionid发生变化。
源码解析:
注销关键代码:
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
            ServletException {
        if (!(request instanceof HttpServletRequest)) {
            throw new ServletException("Can only process HttpServletRequest");
        }

        if (!(response instanceof HttpServletResponse)) {
            throw new ServletException("Can only process HttpServletResponse");
        }

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        if (requiresLogout(httpRequest, httpResponse)) {
            Authentication auth = SecurityContextHolder.getContext().getAuthentication();

            if (logger.isDebugEnabled()) {
                logger.debug("Logging out user '" + auth + "' and redirecting to logout page");
            }

            for (int i = 0; i < handlers.length; i++) {
                handlers[i].logout(httpRequest, httpResponse, auth);
            }

            sendRedirect(httpRequest, httpResponse, logoutSuccessUrl);

            return;
        }

        chain.doFilter(request, response);
    }

其中requiresLogout判断是否需要注销,只要后缀以/j_acegi_logout结尾的就需要。类似登陆时requiresAuthentication判断是否需要验证,只有后缀以/j_acegi_security_check结尾的就需要。
LogoutHandler调用注销:
   public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
Assert.notNull(request, "HttpServletRequest required");
if (invalidateHttpSession) {
HttpSession session = request.getSession(false);
if (session != null) {
session.invalidate();
}
}

SecurityContextHolder.clearContext();
}
其中LogoutHandler可以有多个,默认的是消除session的。比如还有利用cookie登陆的,注销时,是不是得消除cookie,new一个新的cookie即可。

经过上篇博客对session的解析,相信大家很能理解这段话。其中invalidateHttpSession默认是true,HttpSession session = request.getSession(false);获取当前的session,若没有,则返回null。当session!=null时,销毁session—session.invalidate();销毁后,把SecurityContextHolder存放的securitycontext清空。
后续:
OK,写到这篇博客时,相当于一个小小的雏形验证系统了。登陆、验证、注销、异常、投票机制都已经全了,那这几篇中对于userinfo.jsp保护页面设置权限,而非保护页面login.jsp则人人可登陆,调试中我们判断login.jsp中的securitycontext中的权限对象为null,为了完善其验证系统,那我们给不需要验证人人都可以访问的页面一个匿名的权限——待续。
下载:

项目下载:
ps:项目中的index.jsp没用到,可以删除,主要用login.jsp、userinfo.jsp、accessdefined.jsp.


分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics