这是我参与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对象,并且配置一些属性,并将其赋值给当前配置类的logoutHandler和rememberMeServices属性,最后返回。
这里有一点有必要交代,之所以要将创建的 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 相关的认证逻辑,正是在这个过滤器中调用的。