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

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】

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. public LogoutFilter(String logoutSuccessUrl, LogoutHandler… handlers) {
  8. Assert.notEmpty(handlers, “LogoutHandlers are required”);
  9. this.handlers = Arrays.asList(handlers);
  10. Assert.isTrue(!StringUtils.hasLength(logoutSuccessUrl) ||
  11. UrlUtils.isValidRedirectUrl(logoutSuccessUrl), logoutSuccessUrl + ” isn’t a valid redirect URL”);
  12. SimpleUrlLogoutSuccessHandler urlLogoutSuccessHandler = new SimpleUrlLogoutSuccessHandler();
  13. if (StringUtils.hasText(logoutSuccessUrl)) {
  14. urlLogoutSuccessHandler.setDefaultTargetUrl(logoutSuccessUrl);
  15. }
  16. logoutSuccessHandler = urlLogoutSuccessHandler;
  17. }

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

此时应该能知道,在LogoutFilter的bean实例化时,两个类变量logoutSuccessUrl、List 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. if (logger.isDebugEnabled()) {
  10. logger.debug(“Logging out user ‘” + auth + “‘ and transferring to logout destination”);
  11. }
  12. //循环LogoutHandler处理退出任务
  13. for (LogoutHandler handler : handlers) {
  14. handler.logout(request, response, auth);
  15. }
  16. //退出成功后,进行redirect操作
  17. logoutSuccessHandler.onLogoutSuccess(request, response, auth);
  18. return;
  19. }
  20. chain.doFilter(request, response);
  21. }

这时,可能会产生疑问。上一个过滤器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. SecurityContextHolder.clearContext();
  11. }

再来看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. if (authentication != null) {
  5. //如果定义了token-repository-ref属性,则通过依赖的持久化bean清除
  6. //如果定义了data-source-ref属性,直接通过
  7. //JdbcTokenRepositoryImpl清除数据,也就是执行delete操作
  8. tokenRepository.removeUserTokens(authentication.getName());
  9. }
  10. }

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


来源:http://ddrv.cn

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » Spring Security3源码分析(6)-LogoutFilter分析

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏