Spring Security3源码分析-LogoutFilter分析

 2019-11-02 21:22  阅读(1085)
文章分类:Spring boot

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 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,

如果有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

//完成两个任务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();
        }

再来看remember me的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


来源:http://ddrv.cn

点赞(0)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> Spring Security3源码分析-LogoutFilter分析

相关推荐