专栏首页JavaEdgeSpring Security源码分析之LogoutFilter

Spring Security源码分析之LogoutFilter

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Java集合源码解析 - HashMap

    HashMap是基于哈希表实现的,每一个元素是一个key-value对,其内部通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长.

    JavaEdge
  • 突破Java面试(23-9) - 深入解析Redis哨兵底层原理

    通过Redis的pub/sub实现哨兵互相之间的发现,每个哨兵都会往__sentinel__:hello这个channel发一个消息,此时所有其他哨兵都可消费到...

    JavaEdge
  • jacoco关于Java代码覆盖率你不得不会的基操!

    ant是构建工具,内置任务和可选任务组成的.Ant运行时需要一个XML文件(构建文件)。

    JavaEdge
  • if else 太多?看我用 Java 8 轻松干掉!

    要逐个判空再取最后的不为空的值,这样写,如果 if 多了就极不优雅,于是,我利用了 Java 8 的 Optional.map 方法干掉了层层 if,同事直呼看...

    Java技术栈
  • [随缘一题]有效的三角形

    呼延十
  • Nuxt + Koa2 + Mongodb 手撸一个网上商城

    https://www.runoob.com/mongodb/mongodb-osx-install.html

    FinGet
  • Nuxt + Koa2 + Mongodb 手撸一个网上商城

    文档地址:https://finget.github.io/2019/08/06/nuxt-koa-mongodb/

    FinGet
  • 在scss中注释模块结构

    一般来说,有重构的团队,工作流程是这样的:设计师出稿 > 重构转静态页面 > jser拉取数据实现交互等 ...这样我们总是有静态页面在备份的,下次遇到修改什么...

    IMWeb前端团队
  • BZOJ1093: [ZJOI2007]最大半连通子图(tarjan dp)

    attack
  • 【Go 语言社区】Golang内存分配

    golang内存分配 new一个对象的时候,入口函数是malloc.go中的newobject函数 func newobject(typ *_type) uns...

    李海彬

扫码关注云+社区

领取腾讯云代金券