在开启 RememberMe 时,Spring Security 做了什么?

1,402 阅读3分钟

这是我参与2022首次更文挑战的第10天,活动详情查看:2022首次更文挑战

上一篇《Spring Security 是如何“记住我”的? 》介绍了,Spring Security 的 RememberMe 功能的实现原理,我们可以在 Spring Security 的配置类中,对 HttpSecurity 对象调用一个 rememberMe() 方法,就可以开启这个功能。这一篇,我们来试着分析一下,在这个方法中,Spring Security 做了哪些事情。

如果你没读过前面那篇,可以先去读一下。

先从 rememberMe() 方法入手

查看 rememberMe() 方法的源码:

public RememberMeConfigurer<HttpSecurity> rememberMe() throws Exception {
    return getOrApply(new RememberMeConfigurer<>());
}

很简洁,就一句,配置了一个 RememberMeConfigurer,然后,我们重点看这个类。

RememberMeConfigurer

这个配置类里面有两个方法值得注意,一个是 init 方法,另一个是 configure 方法。我们一一来看。

init 方法

先看 init 方法的源码。

@Override
public void init(H http) throws Exception {
   validateInput();
   String key = getKey();
   RememberMeServices rememberMeServices = getRememberMeServices(http, key);
   http.setSharedObject(RememberMeServices.class, rememberMeServices);
   LogoutConfigurer<H> logoutConfigurer = http.getConfigurer(LogoutConfigurer.class);
   if (logoutConfigurer != null && this.logoutHandler != null) {
      logoutConfigurer.addLogoutHandler(this.logoutHandler);
   }
   RememberMeAuthenticationProvider authenticationProvider = new RememberMeAuthenticationProvider(key);
   authenticationProvider = postProcess(authenticationProvider);
   http.authenticationProvider(authenticationProvider);
   initDefaultLoginFilter(http);
}

这个方法里一共干了 3 件事儿,我们一个一个看。

配置 RememberMeServices

RememberMeServices 是通过这样代码创建的:

RememberMeServices rememberMeServices = getRememberMeServices(http, key);

我们找到 getRememberMeServices 的源码查看:

private RememberMeServices getRememberMeServices(H http, String key) throws Exception {
   if (this.rememberMeServices != null) {
      if (this.rememberMeServices instanceof LogoutHandler && this.logoutHandler == null) {
         this.logoutHandler = (LogoutHandler) this.rememberMeServices;
      }
      return this.rememberMeServices;
   }
   AbstractRememberMeServices tokenRememberMeServices = createRememberMeServices(http, key);
   tokenRememberMeServices.setParameter(this.rememberMeParameter);
   tokenRememberMeServices.setCookieName(this.rememberMeCookieName);
   if (this.rememberMeCookieDomain != null) {
        tokenRememberMeServices.setCookieDomain(this.rememberMeCookieDomain);
   }
   if (this.tokenValiditySeconds != null) {
      tokenRememberMeServices.setTokenValiditySeconds(this.tokenValiditySeconds);
   }
   if (this.useSecureCookie != null) {
      tokenRememberMeServices.setUseSecureCookie(this.useSecureCookie);
   }
   if (this.alwaysRemember != null) {
      tokenRememberMeServices.setAlwaysRemember(this.alwaysRemember);
   }
   tokenRememberMeServices.afterPropertiesSet();
   this.logoutHandler = tokenRememberMeServices;
   this.rememberMeServices = tokenRememberMeServices;
   return tokenRememberMeServices;
}

我们从上到下解析:

  • 如果已经配置了 rememberMeServices,则返回,否则继续向下执行。在返回之前,还会判断它是否是 LogoutHandler 的实现类,如果是的话,将其父只给 logoutHandler 属性。
  • 通过 createRememberMeServices 方法,创建一个 AbstractRememberMeServices 对象,并且配置一些属性,并将其赋值给当前配置类的 logoutHandlerrememberMeServices 属性,最后返回。

这里有一点有必要交代,之所以要将创建的 tokenRememberMeServices 赋值给 logoutHandler,是因为 AbstractRememberMeServices 类也实现了 LogoutHandler 接口,这个接口只有一个 logout 方法,实现这个方法后,就可以在用户注销登录之后,删除 RememberMeToken 及相关的信息。

这里我们还需要关注一下 createRememberMeServices 方法,是如何创建 AbstractRememberMeServices 对象的。这个方法的源码如下:

private AbstractRememberMeServices createRememberMeServices(H http, String key) {
   return (this.tokenRepository != null) ? createPersistentRememberMeServices(http, key)
         : createTokenBasedRememberMeServices(http, key);
}

上一篇文章(链接在本文开头)中,我曾经说过:

默认情况下 Spring Security 会使用 TokenBasedRememberMeServices ,提供了基础的功能。如果我们在开启 RememberMe 功能的时候,同时配置了一个 PersistentTokenRepository,那么 Spring Security 会自动选择 PersistentTokenBasedRememberMeServices 的实现。

这个判断逻辑就是在这里完成的。

至此,init 方法中,配置 RememberMeServices 的逻辑,我们就分析到这里。

配置 logoutHandler

接下来,会配置 logoutHandler,我们刚才通过源码分析过,在配置 RememberMeServices 的过程中,已经又了 logoutHandler 属性的值,这里只需要将其配置好,它的作用就是在用户注销登录后,删除 RememberMeToken 及相关的信息。

这一逻辑也可以从 AbstractRememberMeServices 的 logout 方法的源码中看出来:

@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
   this.logger.debug(LogMessage
         .of(() -> "Logout of user " + ((authentication != null) ? authentication.getName() : "Unknown")));
   cancelCookie(request, response);
}

配置 RememberMeAuthenticationProvider

上一篇文章(链接在本文开头)中我们也说到过,在RememberMeAuthenticationFilter 中,需要调用 authenticationManager.authenticate 方法进行身份认证。如果你了解 Spring Security 的认证流程的话就会知道,每一种认证方式都需要有一个对应的 Provider 来完成具体的认证工作。RememberMeAuthenticationProvider 就是来完成 RememberMe 认证的那个 Provider。

想要了解 Spring Security 的认证流程源码分析,可以参考我的这篇文章:Spring Security 认证流程

以上,就是 init 方法完成的工作。接下来看 configure 方法。

configure 方法

看源码:

@Override
public void configure(H http) {
   RememberMeAuthenticationFilter rememberMeFilter = new RememberMeAuthenticationFilter(
         http.getSharedObject(AuthenticationManager.class), this.rememberMeServices);
   if (this.authenticationSuccessHandler != null) {
      rememberMeFilter.setAuthenticationSuccessHandler(this.authenticationSuccessHandler);
   }
   rememberMeFilter = postProcess(rememberMeFilter);
   http.addFilter(rememberMeFilter);
}

这里比较简单,就是通过 AuthenticationManager 和刚才创建的 rememberMeServices 创建了一个 RememberMeAuthenticationFilter 过滤器,并将其加入过滤器链。RememberMe 相关的认证逻辑,正是在这个过滤器中调用的。