LogoutFilter 过滤器对应的类路径为
org.springframework.security.web.authentication.logout.LogoutFilter
通过这个类的源码可以看出, 这个类有两个构造函数
这两个构造函数的参数, 就是之前解析 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");
- }
- this.handler.logout(request, response, auth);
- // 退出成功后, 进行 redirect 操作
- logoutSuccessHandler.onLogoutSuccess(request, response, auth);
- return;
- }
- chain.doFilter(request, response);
- }
上一个过滤器
SecurityContextPersistenceFilter
不是只产生了一个空的 SecurityContext 嘛? 就是一个没有认证信息的 SecurityContext 实例
这个返回的不是 null 么? 产生这个疑问, 肯定是被 SecurityContextPersistenceFilter 过滤器分析时误导的.
实际上, 每个过滤器只处理自己负责的事情, LogoutFilter 只负责拦截 j_spring_security_logout 这个 url(如果没有配置 logout 的 url), 其他的 url 全部跳过.
其实退出功能肯定是登录到应用之后才会使用到的, 登录对应的 Filter 肯定会把认证信息添加到 SecurityContext 中去的, 后面再分析.
继续看 LogoutHandler 是如何处理退出任务的
这里的 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
完成两个任务
让 session 失效
清除 SecurityContext 实例
再来看 remember me 的 handler
1. 配置了持久化属性时的 handler:PersistentTokenBasedRememberMeServices
也完成两个任务
清除 cookie
从持久化中清除 remember me 数据
如果定义了 token-repository-ref 属性, 则通过依赖的持久化 bean 清除
如果定义了 data-source-ref 属性, 直接通过
JdbcTokenRepositoryImpl 清除数据, 也就是执行 delete 操作
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://www.jianshu.com/p/818f7f29f0ba