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) {
-
- 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;
- }
-
- 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));
- }
-
- builder.addConstructorArgReference(successHandlerRef);
- } else {
-
- if (!StringUtils.hasText(logoutSuccessUrl)) {
-
- logoutSuccessUrl = DEF_LOGOUT_SUCCESS_URL;
- }
-
- builder.addConstructorArgValue(logoutSuccessUrl);
- }
-
- if (!StringUtils.hasText(invalidateSession)) {
- invalidateSession = DEF_INVALIDATE_SESSION;
- }
-
- ManagedList handlers = new ManagedList();
- SecurityContextLogoutHandler sclh = new SecurityContextLogoutHandler();
- if ("true".equals(invalidateSession)) {
- sclh.setInvalidateHttpSession(true);
- } else {
- sclh.setInvalidateHttpSession(false);
- }
- handlers.add(sclh);
-
- if (rememberMeServices != null) {
- handlers.add(new RuntimeBeanReference(rememberMeServices));
- }
-
- 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;
-
- if (requiresLogout(request, response)) {
-
- Authentication auth = SecurityContextHolder.getContext().getAuthentication();
-
- if (logger.isDebugEnabled()) {
- logger.debug("Logging out user '" + auth + "' and transferring to logout destination");
- }
-
- for (LogoutHandler handler : handlers) {
- handler.logout(request, response, auth);
- }
-
- 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, 如果有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
-
- 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();
- }
再来看remember me的handler 1.配置了持久化属性时的handler:PersistentTokenBasedRememberMeServices
-
- public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
- super.logout(request, response, authentication);
-
- if (authentication != null) {
-
-
-
- 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
|