Spring Security3源码分析(6)-LogoutFilter分析

LogoutFilter过滤器对应的类路径为 

org.springframework.security.web.authentication.logout.LogoutFilter 

通过这个类的源码可以看出,这个类有两个构造函数 

Java代码  

  1. public LogoutFilter(LogoutSuccessHandler logoutSuccessHandler, LogoutHandler… handlers) {  
  2.     Assert.notEmpty(handlers, “LogoutHandlers are required”);  
  3.     this.handlers = Arrays.asList(handlers);  
  4.     Assert.notNull(logoutSuccessHandler, “logoutSuccessHandler cannot be null”);  
  5.     this.logoutSuccessHandler = logoutSuccessHandler;  
  6. }  
  7.   
  8. public LogoutFilter(String logoutSuccessUrl, LogoutHandler… handlers) {  
  9.     Assert.notEmpty(handlers, “LogoutHandlers are required”);  
  10.     this.handlers = Arrays.asList(handlers);  
  11.     Assert.isTrue(!StringUtils.hasLength(logoutSuccessUrl) ||  
  12.             UrlUtils.isValidRedirectUrl(logoutSuccessUrl), logoutSuccessUrl + ” isn’t a valid redirect URL”);  
  13.     SimpleUrlLogoutSuccessHandler urlLogoutSuccessHandler = new SimpleUrlLogoutSuccessHandler();  
  14.     if (StringUtils.hasText(logoutSuccessUrl)) {  
  15.         urlLogoutSuccessHandler.setDefaultTargetUrl(logoutSuccessUrl);  
  16.     }  
  17.     logoutSuccessHandler = urlLogoutSuccessHandler;  
  18. }  

这两个构造函数的参数,是从哪里传递的呢?没错,就是之前解析http标签通过创建LogoutFilter过滤器的bean定义时通过构造参数注入进来的。下面的部分源码为LogoutFilter的bean定义 

Java代码  

  1. public BeanDefinition parse(Element element, ParserContext pc) {  
  2.         String logoutUrl = null;  
  3.         String successHandlerRef = null;  
  4.         String logoutSuccessUrl = null;  
  5.         String invalidateSession = null;  
  6.   
  7.         BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(LogoutFilter.class);  
  8.   
  9.         if (element != null) {  
  10.             //分别解析logout标签的属性  
  11.             Object source = pc.extractSource(element);  
  12.             builder.getRawBeanDefinition().setSource(source);  
  13.             logoutUrl = element.getAttribute(ATT_LOGOUT_URL);  
  14.             successHandlerRef = element.getAttribute(ATT_LOGOUT_HANDLER);  
  15.             WebConfigUtils.validateHttpRedirect(logoutUrl, pc, source);  
  16.             logoutSuccessUrl = element.getAttribute(ATT_LOGOUT_SUCCESS_URL);  
  17.             WebConfigUtils.validateHttpRedirect(logoutSuccessUrl, pc, source);  
  18.             invalidateSession = element.getAttribute(ATT_INVALIDATE_SESSION);  
  19.         }  
  20.   
  21.         if (!StringUtils.hasText(logoutUrl)) {  
  22.             logoutUrl = DEF_LOGOUT_URL;  
  23.         }  
  24.         //向LogoutFilter中注入属性值filterProcessesUrl  
  25.         builder.addPropertyValue(“filterProcessesUrl”, logoutUrl);  
  26.   
  27.         if (StringUtils.hasText(successHandlerRef)) {  
  28.             if (StringUtils.hasText(logoutSuccessUrl)) {  
  29.                 pc.getReaderContext().error(“Use “ + ATT_LOGOUT_URL + ” or “ + ATT_LOGOUT_HANDLER + “, but not both”,  
  30.                         pc.extractSource(element));  
  31.             }  
  32.             //如果successHandlerRef不为空,就通过构造函数注入到LogoutFilter中  
  33.             builder.addConstructorArgReference(successHandlerRef);  
  34.         } else {  
  35.             // Use the logout URL if no handler set  
  36.             if (!StringUtils.hasText(logoutSuccessUrl)) {  
  37.                 //如果logout-success-url没有定义,则采用默认的/  
  38.                 logoutSuccessUrl = DEF_LOGOUT_SUCCESS_URL;  
  39.             }  
  40.             //通过构造函数注入logoutSuccessUrl值  
  41.             builder.addConstructorArgValue(logoutSuccessUrl);  
  42.         }  
  43.   
  44.         if (!StringUtils.hasText(invalidateSession)) {  
  45.             invalidateSession = DEF_INVALIDATE_SESSION;  
  46.         }  
  47.         //默认Logout的Handler是SecurityContextLogoutHandler  
  48.         ManagedList handlers = new ManagedList();  
  49.         SecurityContextLogoutHandler sclh = new SecurityContextLogoutHandler();  
  50.         if (“true”.equals(invalidateSession)) {  
  51.             sclh.setInvalidateHttpSession(true);  
  52.         } else {  
  53.             sclh.setInvalidateHttpSession(false);  
  54.         }  
  55.         handlers.add(sclh);  
  56.         //如果有remember me服务,需要添加remember的handler  
  57.         if (rememberMeServices != null) {  
  58.             handlers.add(new RuntimeBeanReference(rememberMeServices));  
  59.         }  
  60.         //继续将handlers通过构造参数注入到LogoutFilter的bean中  
  61.         builder.addConstructorArgValue(handlers);  
  62.   
  63.         return builder.getBeanDefinition();  
  64.     }  

此时应该能知道,在LogoutFilter的bean实例化时,两个类变量logoutSuccessUrl、List<LogoutHandler> handlers已经通过构造函数注入到LogoutFilter的实例中来了。 

接下来,继续看doFilter部分的源码 

Java代码  

  1. public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)  
  2.         throws IOException, ServletException {  
  3.     HttpServletRequest request = (HttpServletRequest) req;  
  4.     HttpServletResponse response = (HttpServletResponse) res;  
  5.     //判断是否需要退出,主要通过请求的url是否是filterProcessesUrl值来识别  
  6.     if (requiresLogout(request, response)) {  
  7.         //通过SecurityContext实例获取认证信息  
  8.         Authentication auth = SecurityContextHolder.getContext().getAuthentication();  
  9.   
  10.         if (logger.isDebugEnabled()) {  
  11.             logger.debug(“Logging out user ‘” + auth + “‘ and transferring to logout destination”);  
  12.         }  
  13.         //循环LogoutHandler处理退出任务  
  14.         for (LogoutHandler handler : handlers) {  
  15.             handler.logout(request, response, auth);  
  16.         }  
  17.         //退出成功后,进行redirect操作  
  18.         logoutSuccessHandler.onLogoutSuccess(request, response, auth);  
  19.   
  20.         return;  
  21.     }  
  22.   
  23.     chain.doFilter(request, response);  
  24. }  

这时,可能会产生疑问。上一个过滤器SecurityContextPersistenceFilter不是只产生了一个空的SecurityContext么?就是一个没有认证信息的SecurityContext实例 

Java代码  

  1. Authentication auth = SecurityContextHolder.getContext().getAuthentication();  

这个返回的不是null么?产生这个疑问,肯定是被SecurityContextPersistenceFilter过滤器分析时误导的。实际上,每个过滤器只处理自己负责的事情,LogoutFilter只负责拦截j_spring_security_logout这个url(如果没有配置logout的url),其他的url全部跳过。其实退出功能肯定是登录到应用之后才会使用到的,登录对应的Filter肯定会把认证信息添加到SecurityContext中去的,后面再分析。 

继续看LogoutHandler是如何处理退出任务的 

Java代码  

  1. for (LogoutHandler handler : handlers) {  
  2.     handler.logout(request, response, auth);  
  3. }  

这里的handler至少有一个SecurityContextLogoutHandler, 

如果有remember me服务,就还有一个Handler。remember me的handler有两种, 

如果配置了持久化信息,如(token-repository-ref、data-source-ref属性)这种的handler为:org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices 

如果没有配置,那么handler就是:org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices 

先来看SecurityContextLogoutHandler 

Java代码  

  1. //完成两个任务1.让session失效;2.清除SecurityContext实例  
  2. public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {  
  3.     Assert.notNull(request, “HttpServletRequest required”);  
  4.     if (invalidateHttpSession) {  
  5.         HttpSession session = request.getSession(false);  
  6.         if (session != null) {  
  7.             session.invalidate();  
  8.         }  
  9.     }  
  10.   
  11.     SecurityContextHolder.clearContext();  
  12. }  

再来看remember me的handler 

1.配置了持久化属性时的handler:PersistentTokenBasedRememberMeServices 

Java代码  

  1. //也完成两个任务1.清除cookie;2.从持久化中清除remember me数据  
  2. public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {  
  3.     super.logout(request, response, authentication);  
  4.   
  5.     if (authentication != null) {  
  6.         //如果定义了token-repository-ref属性,则通过依赖的持久化bean清除  
  7.           //如果定义了data-source-ref属性,直接通过  
  8.           //JdbcTokenRepositoryImpl清除数据,也就是执行delete操作  
  9.         tokenRepository.removeUserTokens(authentication.getName());  
  10.     }  
  11. }  

2.未配置持久化属性的handler:TokenBasedRememberMeServices 

这个handler没有覆盖父类的logout方法,所以直接调用父类的logout方法,仅仅清除cookie 

退出成功后执行onLogoutSuccess操作,完成redirect 

Java代码  

  1. logoutSuccessHandler.onLogoutSuccess(request, response, auth);  

这个语句是直接redirect到logout标签中的logout-success-url属性定义的url 

至此,整个logoutFilter任务已经完成了,总结一下,主要任务为 

1.从SecurityContext中获取Authentication,然后调用每个handler处理logout 

2.退出成功后跳转到指定的url

    原文作者:Spring Boot
    原文地址: https://blog.csdn.net/benjamin_whx/article/details/39204629
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞