Spring Security3源码分析-LogoutFilter分析
LogoutFilter过滤器对应的类路径为
org.springframework.security.web.authentication.logout.LogoutFilter
通过这个类的源码可以看出,这个类有两个构造函数
public LogoutFilter(LogoutSuccessHandler logoutSuccessHandler, LogoutHandler... handlers) { Assert.notEmpty(handlers, "LogoutHandlers are required"); this.handlers = Arrays.asList(handlers); Assert.notNull(logoutSuccessHandler, "logoutSuccessHandler cannot be null"); this.logoutSuccessHandler = logoutSuccessHandler; } public LogoutFilter(String logoutSuccessUrl, LogoutHandler... handlers) { Assert.notEmpty(handlers, "LogoutHandlers are required"); this.handlers = Arrays.asList(handlers); Assert.isTrue(!StringUtils.hasLength(logoutSuccessUrl) || UrlUtils.isValidRedirectUrl(logoutSuccessUrl), logoutSuccessUrl + " isn't a valid redirect URL"); SimpleUrlLogoutSuccessHandler urlLogoutSuccessHandler = new SimpleUrlLogoutSuccessHandler(); if (StringUtils.hasText(logoutSuccessUrl)) { urlLogoutSuccessHandler.setDefaultTargetUrl(logoutSuccessUrl); } logoutSuccessHandler = urlLogoutSuccessHandler; }
这两个构造函数的参数,是从哪里传递的呢?没错,就是之前解析http标签通过创建LogoutFilter过滤器的bean定义时通过构造参数注入进来的。下面的部分源码为LogoutFilter的bean定义
public BeanDefinition parse(Element element, ParserContext pc) { String logoutUrl = null; String successHandlerRef = null; String logoutSuccessUrl = null; String invalidateSession = null; BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(LogoutFilter.class); if (element != null) { //分别解析logout标签的属性 Object source = pc.extractSource(element); builder.getRawBeanDefinition().setSource(source); logoutUrl = element.getAttribute(ATT_LOGOUT_URL); successHandlerRef = element.getAttribute(ATT_LOGOUT_HANDLER); WebConfigUtils.validateHttpRedirect(logoutUrl, pc, source); logoutSuccessUrl = element.getAttribute(ATT_LOGOUT_SUCCESS_URL); WebConfigUtils.validateHttpRedirect(logoutSuccessUrl, pc, source); invalidateSession = element.getAttribute(ATT_INVALIDATE_SESSION); } if (!StringUtils.hasText(logoutUrl)) { logoutUrl = DEF_LOGOUT_URL; } //向LogoutFilter中注入属性值filterProcessesUrl builder.addPropertyValue("filterProcessesUrl", logoutUrl); if (StringUtils.hasText(successHandlerRef)) { if (StringUtils.hasText(logoutSuccessUrl)) { pc.getReaderContext().error("Use " + ATT_LOGOUT_URL + " or " + ATT_LOGOUT_HANDLER + ", but not both", pc.extractSource(element)); } //如果successHandlerRef不为空,就通过构造函数注入到LogoutFilter中 builder.addConstructorArgReference(successHandlerRef); } else { // Use the logout URL if no handler set if (!StringUtils.hasText(logoutSuccessUrl)) { //如果logout-success-url没有定义,则采用默认的/ logoutSuccessUrl = DEF_LOGOUT_SUCCESS_URL; } //通过构造函数注入logoutSuccessUrl值 builder.addConstructorArgValue(logoutSuccessUrl); } if (!StringUtils.hasText(invalidateSession)) { invalidateSession = DEF_INVALIDATE_SESSION; } //默认Logout的Handler是SecurityContextLogoutHandler ManagedList handlers = new ManagedList(); SecurityContextLogoutHandler sclh = new SecurityContextLogoutHandler(); if ("true".equals(invalidateSession)) { sclh.setInvalidateHttpSession(true); } else { sclh.setInvalidateHttpSession(false); } handlers.add(sclh); //如果有remember me服务,需要添加remember的handler if (rememberMeServices != null) { handlers.add(new RuntimeBeanReference(rememberMeServices)); } //继续将handlers通过构造参数注入到LogoutFilter的bean中 builder.addConstructorArgValue(handlers); return builder.getBeanDefinition(); }
此时应该能知道,在LogoutFilter的bean实例化时,两个类变量logoutSuccessUrl、List<LogoutHandler>handlers已经通过构造函数注入到LogoutFilter的实例中来了。
接下来,继续看doFilter部分的源码
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; //判断是否需要退出,主要通过请求的url是否是filterProcessesUrl值来识别 if (requiresLogout(request, response)) { //通过SecurityContext实例获取认证信息 Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (logger.isDebugEnabled()) { logger.debug("Logging out user '" + auth + "' and transferring to logout destination"); } //循环LogoutHandler处理退出任务 for (LogoutHandler handler : handlers) { handler.logout(request, response, auth); } //退出成功后,进行redirect操作 logoutSuccessHandler.onLogoutSuccess(request, response, auth); return; } chain.doFilter(request, response); }
这时,可能会产生疑问。上一个过滤器SecurityContextPersistenceFilter不是只产生了一个空的SecurityContext么?就是一个没有认证信息的SecurityContext实例
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
这个返回的不是null么?产生这个疑问,肯定是被SecurityContextPersistenceFilter过滤器分析时误导的。实际上,每个过滤器只处理自己负责的事情,LogoutFilter只负责拦截j_spring_security_logout这个url(如果没有配置logout的url),其他的url全部跳过。其实退出功能肯定是登录到应用之后才会使用到的,登录对应的Filter肯定会把认证信息添加到SecurityContext中去的,后面再分析。
继续看LogoutHandler是如何处理退出任务的
for (LogoutHandler handler : handlers) { handler.logout(request, response, auth); }
这里的handler至少有一个SecurityContextLogoutHandler,
如果有rememberme服务,就还有一个Handler。rememberme的handler有两种,
如果配置了持久化信息,如(token-repository-ref、data-source-ref属性)这种的handler为:org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices
如果没有配置,那么handler就是:org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices
先来看SecurityContextLogoutHandler
//完成两个任务1.让session失效;2.清除SecurityContext实例 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(); }
再来看rememberme的handler
1.配置了持久化属性时的handler:PersistentTokenBasedRememberMeServices
//也完成两个任务1.清除cookie;2.从持久化中清除remember me数据 public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { super.logout(request, response, authentication); if (authentication != null) { //如果定义了token-repository-ref属性,则通过依赖的持久化bean清除 //如果定义了data-source-ref属性,直接通过 //JdbcTokenRepositoryImpl清除数据,也就是执行delete操作 tokenRepository.removeUserTokens(authentication.getName()); } }
2.未配置持久化属性的handler:TokenBasedRememberMeServices
这个handler没有覆盖父类的logout方法,所以直接调用父类的logout方法,仅仅清除cookie
退出成功后执行onLogoutSuccess操作,完成redirect
logoutSuccessHandler.onLogoutSuccess(request, response, auth);
这个语句是直接redirect到logout标签中的logout-success-url属性定义的url
至此,整个logoutFilter任务已经完成了,总结一下,主要任务为
1.从SecurityContext中获取Authentication,然后调用每个handler处理logout
2.退出成功后跳转到指定的url