首页 > 编程语言 >[SpringSecurity5.6.2源码分析十三]:LogoutFilter

[SpringSecurity5.6.2源码分析十三]:LogoutFilter

时间:2023-09-22 17:32:11浏览次数:47  
标签:SpringSecurity5.6 request LogoutFilter public authentication 源码 登出 null response

前言

  • • SpringSecurity默认提供了登录的页面以及登录的接口,与之对应的也提供了登出页和登出请求
  • • 登出请求对应的过滤器是LogoutFilter
  • • 登出页对应的是DefaultLogoutPageGeneratingFilter、

1. LogoutConfigurer

  • • LogoutConfigurer是LogoutFilter对应的配置类,先看其主要方法

1.1 addLogoutHandler(...)

  • • 为LogoutFilter添加对应的登出处理器(LogoutHandler)
public LogoutConfigurer<H> addLogoutHandler(LogoutHandler logoutHandler) {
   Assert.notNull(logoutHandler, "logoutHandler cannot be null");
   this.logoutHandlers.add(logoutHandler);
   return this;
}
  • • LogoutHandler作为登出时的主要操作对象
public interface LogoutHandler {

   /**
    * 进行登出操作,比如说清除Csrf的令牌
    * @param request the HTTP request
    * @param response the HTTP response
    * @param authentication the current principal details
    */
   void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication);

}
  • • 其子类有很多

1.1.1 CookieClearingLogoutHandler

  • • 如果说我们有一些比较重要的Cookie需要在退出登录后清除,那就可以用到CookieClearingLogoutHandler,有两种清空处理方式
  • • 将指定Cookie的值设置为空
  • • 将指定Cookie的生存时间设置为0
public final class CookieClearingLogoutHandler implements LogoutHandler {

   private final List<Function<HttpServletRequest, Cookie>> cookiesToClear;

   /**
    * 将指定Cookie的值设置为空
    * @param cookiesToClear 需要清除Cookie的名称
    */
   public CookieClearingLogoutHandler(String... cookiesToClear) {
      Assert.notNull(cookiesToClear, "List of cookies cannot be null");
      List<Function<HttpServletRequest, Cookie>> cookieList = new ArrayList<>();
      for (String cookieName : cookiesToClear) {
         //添加清除函数
         cookieList.add((request) -> {
            //这里将指定名称的Cookie的Value设置为空
            Cookie cookie = new Cookie(cookieName, null);
            String contextPath = request.getContextPath();
            String cookiePath = StringUtils.hasText(contextPath) ? contextPath : "/";
            cookie.setPath(cookiePath);
            cookie.setMaxAge(0);
            //表明只能使用Https或者SSL
            cookie.setSecure(request.isSecure());
            return cookie;
         });
      }
      this.cookiesToClear = cookieList;
   }

   /**
    * 将指定Cookie的生存时间设置为0
    * @param cookiesToClear
    */
   public CookieClearingLogoutHandler(Cookie... cookiesToClear) {
      Assert.notNull(cookiesToClear, "List of cookies cannot be null");
      List<Function<HttpServletRequest, Cookie>> cookieList = new ArrayList<>();
      for (Cookie cookie : cookiesToClear) {
         Assert.isTrue(cookie.getMaxAge() == 0, "Cookie maxAge must be 0");
         cookieList.add((request) -> cookie);
      }
      this.cookiesToClear = cookieList;
   }

   @Override
   public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
      this.cookiesToClear.forEach((f) -> response.addCookie(f.apply(request)));
   }

}

1.1.2 CsrfLogoutHandler

  • • 上篇文章已经介绍过了

1.1.3 HeaderWriterLogoutHandler

  • • HeaderWriterLogoutHandler:将指定请求头写入响应头的登出处理器
public final class HeaderWriterLogoutHandler implements LogoutHandler {

   private final HeaderWriter headerWriter;

   /**
    * Constructs a new instance using the passed {@link HeaderWriter} implementation
    * @param headerWriter
    * @throws IllegalArgumentException if headerWriter is null.
    */
   public HeaderWriterLogoutHandler(HeaderWriter headerWriter) {
      Assert.notNull(headerWriter, "headerWriter cannot be null");
      this.headerWriter = headerWriter;
   }

   @Override
   public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
      this.headerWriter.writeHeaders(request, response);
   }

}

1.1.4 LogoutSuccessEventPublishingLogoutHandler

  • • LogoutSuccessEventPublishingLogoutHandler:发布登出事件的登出处理器
  • • 也是默认注册的LogoutHandler之一
public final class LogoutSuccessEventPublishingLogoutHandler implements LogoutHandler, ApplicationEventPublisherAware {

   private ApplicationEventPublisher eventPublisher;

   @Override
   public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
      if (this.eventPublisher == null) {
         return;
      }
      if (authentication == null) {
         return;
      }
      this.eventPublisher.publishEvent(new LogoutSuccessEvent(authentication));
   }

   @Override
   public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
      this.eventPublisher = applicationEventPublisher;
   }

}

1.1.5 PersistentTokenBasedRememberMeServices && TokenBasedRememberMeServices

  • • SpringSecurity支持记住我机制,本质上也是提供一个记住我令牌到Cookie中,所以说需要在登出的时候删除存储在服务器的记住我令牌,这两个也正是干这个的
  • • 由于此类并不仅仅是干这个的,所以说只贴出有关于登出的代码
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
   super.logout(request, response, authentication);
   if (authentication != null) {
      this.tokenRepository.removeUserTokens(authentication.getName());
   }
}

1.1.6 SecurityContextLogoutHandler

  • • SecurityContextLogoutHandler:SecurityContext作为SpringSecurity的核心类,保存了认证信息和用户信息,是至关重要的,所以说需要在登出的时候有一个类负责清空SecurityContext
  • • 同时这也是默认的登出处理器之一
public class SecurityContextLogoutHandler implements LogoutHandler {

   /**
    * 是否应该在登出时 使Session无效
    */
   private boolean invalidateHttpSession = true;

   /**
    * 是否应该在登出时 清除认证对象
    */
   private boolean clearAuthentication = true;

   /**
    * 清空当前用户的安全上下文
    * @param request the HTTP request
    * @param response the HTTP response
    * @param authentication the current principal details
    */
   @Override
   public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
      Assert.notNull(request, "HttpServletRequest required");
      //是否使Session无效
      if (this.invalidateHttpSession) {
         HttpSession session = request.getSession(false);
         if (session != null) {
            session.invalidate();
            if (this.logger.isDebugEnabled()) {
               this.logger.debug(LogMessage.format("Invalidated session %s", session.getId()));
            }
         }
      }

      //清空当前用户的 线程级别的安全上下文
      //HttpSession级别的在SecurityContextPersistenceFilter中会被清除
      SecurityContext context = SecurityContextHolder.getContext();
      SecurityContextHolder.clearContext();
      //清空认证对象
      if (this.clearAuthentication) {
         context.setAuthentication(null);
      }
   }
}

1.2 logoutSuccessHandler(...)

  • • logoutSuccessHandler(...):配置登出成功后该干嘛,比如说跳转到哪个页面
public LogoutConfigurer<H> logoutSuccessHandler(LogoutSuccessHandler logoutSuccessHandler) {
   this.logoutSuccessUrl = null;
   this.customLogoutSuccess = true;
   this.logoutSuccessHandler = logoutSuccessHandler;
   return this;
}
  • • 其两个实现类一个是转发,一个是设置响应码,都很简单就不做介绍了
  • • ForwardLogoutSuccessHandler
  • • HttpStatusReturningLogoutSuccessHandler

1.3 init(...)

  • • 讲这个方法之前,先来回顾下SpringSsecurity的构建流程
  • • 可以看出是先执行init()方法才会执行configure()方法;
@Override
protected final O doBuild() throws Exception {
   synchronized (this.configurers) {
      this.buildState = BuildState.INITIALIZING;
      beforeInit();
      init();
      this.buildState = BuildState.CONFIGURING;
      beforeConfigure();
      configure();
      this.buildState = BuildState.BUILDING;
      O result = performBuild();
      this.buildState = BuildState.BUILT;
      return result;
   }
}
  • • 我们再来看init(...)的源码
  • • 代码很少就是放行登出请求的Url以及将登出成功Ulr放到登录页过滤器中
@Override
public void init(H http) {
   //如果允许放行
   if (this.permitAll) {
      //两个都是放行登出成功Url
      PermitAllSupport.permitAll(http, this.logoutSuccessUrl);
      PermitAllSupport.permitAll(http, this.getLogoutRequestMatcher(http));
   }

   DefaultLoginPageGeneratingFilter loginPageGeneratingFilter = http
         .getSharedObject(DefaultLoginPageGeneratingFilter.class);
   //当有登录页配置类的时候并且用户没有自定义了登出成功跳转的Url/处理器
   if (loginPageGeneratingFilter != null && !isCustomLogoutSuccess()) {
      //设置登录页的登出成功Url
      loginPageGeneratingFilter.setLogoutSuccessUrl(getLogoutSuccessUrl());
   }
}
  • • 至于为什么要将登出成功Ulr放到登录页过滤器中,看下面的代码,这是在登录页过滤器中
public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {

    private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
          throws IOException, ServletException {
       //是否是认证失败Url的请求
       boolean loginError = isErrorPage(request);
       //是否是登出成功的请求
       boolean logoutSuccess = isLogoutSuccess(request);
       //判断是否需要生产登录页
       if (isLoginUrlRequest(request) || loginError || logoutSuccess) {
          String loginPageHtml = generateLoginPageHtml(request, loginError, logoutSuccess);
          response.setContentType("text/html;charset=UTF-8");
          response.setContentLength(loginPageHtml.getBytes(StandardCharsets.UTF_8).length);
          response.getWriter().write(loginPageHtml);
          return;
       }
       chain.doFilter(request, response);
    }
}
  • • 如果说登出的url是靠ForwardLogoutSuccessHandler进行转发的,那么就又会进入过滤器链,这个时候DefaultLoginPageGeneratingFilter会干嘛?
  • • 很明显会直接生成登录页的Html代码返回给浏览器

1.4 configure(...)

  • • configure(...)的代码很少,主要集中在createLogoutFilter(http)方法中
@Override
public void configure(H http) throws Exception {
   LogoutFilter logoutFilter = createLogoutFilter(http);
   http.addFilter(logoutFilter);
}
  • • createLogoutFilter(...)方法的代码无非就是将我前面讲的类封装到LogoutFilter中
private LogoutFilter createLogoutFilter(H http) {
   //添加登出处理器
   this.logoutHandlers.add(this.contextLogoutHandler);
   //这里多执行了postProcess()方法,是因为这个登出处理器需要一个ApplicationEventPublisher
   this.logoutHandlers.add(postProcess(new LogoutSuccessEventPublishingLogoutHandler()));

   //所有的登出处理器
   LogoutHandler[] handlers = this.logoutHandlers.toArray(new LogoutHandler[0]);
   //创建过滤器
   LogoutFilter result = new LogoutFilter(
         //获得登出成功处理器
         getLogoutSuccessHandler()
         , handlers);
   //设置登出请求的匹配器
   result.setLogoutRequestMatcher(getLogoutRequestMatcher(http));
   result = postProcess(result);
   return result;
}

2. LogoutFilter

  • • 直接上核心方法,原理很简单
  • • 先执行登出处理器
  • • 再执行登出成功处理器
  • • 判断是否是登出请求,如果是
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
      throws IOException, ServletException {
   //判断是否是登出请求
   if (requiresLogout(request, response)) {
      Authentication auth = SecurityContextHolder.getContext().getAuthentication();
      if (this.logger.isDebugEnabled()) {
         this.logger.debug(LogMessage.format("Logging out [%s]", auth));
      }
      //先执行登出处理器
      this.handler.logout(request, response, auth);
      //再执行登出成功处理器
      this.logoutSuccessHandler.onLogoutSuccess(request, response, auth);
      return;
   }
   chain.doFilter(request, response);
}


标签:SpringSecurity5.6,request,LogoutFilter,public,authentication,源码,登出,null,response
From: https://blog.51cto.com/u_14008019/7569346

相关文章

  • 开发看广告app软件包源码+搭建+开发+全包上线
      移动互联网的手机上,你会发现到处都是广告位,就是因为有需求,实现了用户的某一些功能,许多开发者正在寻求开发看广告App软件包。其中,一个关键的功能就是源码功能。  在开发app看广告软件时,保证源码的完整性,通过编写代码让开发者具有更多的软件功能,并确保其高效、安全和可靠......
  • drf - 基于自定义表编写认证类、jwt源码剖析
    补充点翻译函数; 只要做了国际化处理,就会显示当前国家的语言fromdjango.utils.translationimportgettext_lazyas_msg=_('Signaturehasexpired.')#_是函数的别名,这个函数是翻译函数,只要做了国际化处理,它就是中文基于自定义表编写认证类classAuthAuthent......
  • 基于Java web的动力租车管理系统的设计与实现-计算机毕业设计源码+LW文档
    DESIGNANDIMPLEMENTATIONOFPOWERCARRENTALMANAGEMENTSYSTEMBASEDONJAVAWEB ABSTRACTWiththerapiddevelopmentofInternettechnology,thecurrentlifestyleofpeoplehasundergonetremendouschanges.Especiallyundertheinfluenceofthesharing......
  • "强大的在线客服系统解决方案,网站客服源码下载,微信客服一键接入,私有部署可定制,在线客
    在线客服系统的重要性及推荐使用Gofly.v1kf.com在现代商务环境中,提供高质量的客户服务是企业获得成功的关键之一。随着互联网的快速发展,越来越多的企业认识到,拥有一个高效的在线客服系统对于与客户进行快速、便捷的沟通和解决问题至关重要。在选择适合自己企业的在线客服系统时,需......
  • 提供最好用的在线客服系统源码下载,支持独立私有部署,适用于网站、微信、公众号小程序和
    唯一客服系统推荐:gofly.v1kf.com在当今资讯爆炸的时代,对于企业来说,提供良好的客户服务已经成为了一项不可或缺的竞争优势。而在线客服系统无疑成为了企业与客户之间进行沟通和交流的重要工具。本文将向您推荐一款功能强大、易于使用且高度灵活的唯一客服系统——gofly.v1kf.com。......
  • 非常简洁好看的APP软件下载导航网站源码
       非常简洁好看的APP软件下载导航网站源码/APP分享下载页引流导航网站源码带后台版,这款源码 安装非常便捷干净,源码只有十几兆只需要上传源码修改连接信息即可。   后台添加应用及轮播广告也非常方便,小白看了都会!tp的后台响应也特别丝滑,压缩包内附详细安装说明~  ......
  • (新版)抖音最近很火的游戏直播:挤地铁教程+源码+软件
    抖音最近很火的游戏直播:挤地铁教程+源码+软件先上车先吃肉,卡好后带货,卖号,引私域,接星途广告,接小程序广告,带小游戏赚收益均可     免费下载压缩包,提取码:9jbw......
  • 每日一题:vue3自定义指令大全(呕心沥血所作,附可运行项目源码)
    1.VUE常用指令大全本项目所有指令均为全局注册,使用时直接在组件中使用即可。指令目录:src/directives页面目录:src/views具体可查看源码1.1权限指令封装一个权限指令,在模板中根据用户权限来控制元素的显示或隐藏。permission.jsimport{ref,watchEffect}from'vue';c......
  • zabbix 源码编译安装找不到mysql_config
    1.准备LNMP环境,2.下载并解压zabbix包:下载地址:https://sourceforge.net/p/zabbix/activity/?page=0&limit=100#5e836904f0d3473e24304e3d解压  tar-zxvfzabbix-4.0.33.tar.gz3.安装依赖:yuminstalllibxml2-develnet-snmp-devellibevent-develcurl-develpcre*4../configure......
  • 如何优化和开源定制知识付费系统源码
    众所周知,要成功运营一个知识付费平台,需要不断优化和定制系统源码,以满足用户需求并提供出色的用户体验。本文将介绍如何优化和开源定制知识付费系统源码,以便更好地适应市场和用户需求。 第一步:选择合适的知识付费系统源码选择合适的知识付费系统源码是成功的第一步。第二步:定制化用......