文章目录
SpringBoot Secruity RememberMe
Spring Security 实现“记住我”功能,即自动登录功能有两种方式:
- 将 token写入到浏览器的 Cookie中
- 将 token持久化到数据库
1、将 token写入到浏览器的 Cookie中
1.1、演示
下面进行一个简单的小测试,查看Cookie remember-me的值
1、后端
Spring Security默认是没有开启“记住我”功能,我们在 Spring Security配置类中开启它即可。
// 开启记住我功能
.rememberMe()
.key("rememberMeKey") // 默认 key为UUID,我们自定义 key
.tokenValiditySeconds(60) //设置token的过期时间,默认2周
注意: key 默认值是一个 UUID 字符串,如果服务端重启,这个 key 会变,这样会导致之前所有 remember-me 自动登录令牌失效,所以,我们一般都指定 key值。
2、前端
前端需要注意:
- 记住我的字段名称 默认是
remember-me
- remember-me的值必须是
true | on | yes | "1"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/login" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
<!-- 记住我:<input type="checkbox" name="remember-me" value="true"><br>-->
<input type="submit" value="登录">
</form>
</body>
</html>
3、测试
测试一下,登录认证通过后,关掉浏览器,再次打开页面,remember-me功能生效了,就这么简单。
4、remember-me的值
我们将 remember-me的值,通过 Base64 转码后的字符串,得到:
可以看到,cookie 中 remember-me 的使用用 : 隔开,分成了三部分:
- 用户名。
- 时间戳,即 token的过期时间。
- 是使用 MD5 散列函数算出来的值,它的明文格式是 username + “:” + tokenExpiryTime + “:” + password + “:” + key,最后的 key 是一个散列盐值,可以用来防治令牌被修改。
1.2、源码分析
1.2.1、记住我
在Spring Secruity中,登录认证流程成功认证后,就会调用“记住我”功能。
- Spring Security登录认证源码分析:https://blog.csdn.net/qq_42402854/article/details/122295175
查看 UsernamePasswordAuthenticationFilter 的父类 AbstractAuthenticationProcessingFilter 过滤器的 doFIlter方法中,认证做了两个分支
- 成功执行 successfulAuthentication,
- 失败执行 unsuccessfulAuthentication。
1、successfulAuthentication
在 successfulAuthentication内部,将用户认证信息存储到了 SecurityContext中,并调用了 loginSuccess方法,这就是“记住我”功能的核心方法。
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authResult);
SecurityContextHolder.setContext(context);
this.securityContextRepository.saveContext(context, request, response);
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult));
}
//核心方法!
this.rememberMeServices.loginSuccess(request, response, authResult);
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
}
this.successHandler.onAuthenticationSuccess(request, response, authResult);
}
2、unsuccessfulAuthentication
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
SecurityContextHolder.clearContext();
this.logger.trace("Failed to process authentication request", failed);
this.logger.trace("Cleared SecurityContextHolder");
this.logger.trace("Handling authentication failure");
this.rememberMeServices.loginFail(request, response);
this.failureHandler.onAuthenticationFailure(request, response, failed);
}
1.2.2、token的生成
查看 AbstractRememberMeServices类的 loginSuccess方法
。
private String parameter = "remember-me";
public final void loginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
// 判断是否勾选记住我
// 注意:这里this.parameter点进去是上面的private String parameter = "remember-me";
if (!this.rememberMeRequested(request, this.parameter)) {
this.logger.debug("Remember-me login not requested.");
} else {
//若勾选就调用onLoginSuccess方法
this.onLoginSuccess(request, response, successfulAuthentication);
}
}
1、“记住我”表单属性的名称和值
!this.rememberMeRequested(request, this.parameter)
protected boolean rememberMeRequested(HttpServletRequest request, String parameter) {
if (this.alwaysRemember) {
return true;
} else {
// "remember-me"属性名默认为"remember-me"
String paramValue = request.getParameter(parameter);
// 这属性值可以为:true,on,yes,1。
if (paramValue != null && (paramValue.equalsIgnoreCase("true") ||
paramValue.equalsIgnoreCase("on") || paramValue.equalsIgnoreCase("yes") ||
paramValue.equals("1"))) {
//满足上面条件才能返回true
return true;
} else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Did not send remember-me cookie (principal did not set
parameter '" + parameter + "')");
}
return false;
}
}
}
2、查看 TokenBasedRememberMeServices类的 onLoginSuccess方法。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wuQsYmly-1666963429682)(C:\Users\25712\AppData\Roaming\Typora\typora-user-images\image-20221028160538691.png)]
public void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
// 1.获取用户信息
String username = this.retrieveUserName(successfulAuthentication);
String password = this.retrievePassword(successfulAuthentication);
if (!StringUtils.hasLength(username)) {
this.logger.debug("Unable to retrieve username");
} else {
if (!StringUtils.hasLength(password)) {
UserDetails user = this.getUserDetailsService().loadUserByUsername(username);
password = user.getPassword();
if (!StringUtils.hasLength(password)) {
this.logger.debug("Unable to obtain password for user: " + username);
return;
}
}
// 2.获取token的有效期
int tokenLifetime = this.calculateLoginLifetime(request, successfulAuthentication);
long expiryTime = System.currentTimeMillis();
expiryTime += 1000L * (long)(tokenLifetime < 0 ? 1209600 : tokenLifetime);
// 3.生成MD5签名值
String signatureValue = this.makeTokenSignature(expiryTime, username, password);
// 4.将信息添加到浏览器的 Cookie中
this.setCookie(new String[]{username, Long.toString(expiryTime), signatureValue}, tokenLifetime, request, response);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Added remember-me cookie for user '" + username + "', expiry: '" + new Date(expiryTime) + "'");
}
}
}
onLoginSuccess() 方法的逻辑如下:
-
获取用户信息
从登录成功的 Authentication 中提取出用户名/密码。
由于登录成功之后,密码可能被擦除了,所以,如果一开始没有拿到密码,就再从 UserDetailsService 中重新加载用户并重新获取密码。 -
获取token的有效期,令牌有效期默认就是两周。
-
生成MD5签名值
调用 makeTokenSignat ure 方法去计算散列值,实际上就是根据 username、令牌有效期以及 password、key 一起计算一个散列值。如果我们没有自己去设置这个 key,默认是在 RememberMeConfigurer#getKey 方法中进行设置的,它的值是一个 UUID 字符串。
makeTokenSignature:
protected String makeTokenSignature(long tokenExpiryTime, String username, String password) { String data = username + ":" + tokenExpiryTime + ":" + password + ":" + this.getKey(); try { MessageDigest digest = MessageDigest.getInstance("MD5"); return new String(Hex.encode(digest.digest(data.getBytes()))); } catch (NoSuchAlgorithmException var7) { throw new IllegalStateException("No MD5 algorithm available!"); } }
-
将用户名、令牌有效期以及计算得到的散列值,,生成 token值 并添加到浏览器的 Cookie中。
如下:
setCookie
protected void setCookie(String[] tokens, int maxAge, HttpServletRequest request, HttpServletResponse response) { // encodeCookie String cookieValue = this.encodeCookie(tokens); Cookie cookie = new Cookie(this.cookieName, cookieValue); cookie.setMaxAge(maxAge); cookie.setPath(this.getCookiePath(request)); if (this.cookieDomain != null) { cookie.setDomain(this.cookieDomain); } if (maxAge < 1) { cookie.setVersion(1); } cookie.setSecure(this.useSecureCookie != null ? this.useSecureCookie : request.isSecure()); cookie.setHttpOnly(true); response.addCookie(cookie); }
encodeCookie
protected String encodeCookie(String[] cookieTokens) { StringBuilder sb = new StringBuilder(); for(int i = 0; i < cookieTokens.length; ++i) { try { sb.append(URLEncoder.encode(cookieTokens[i], StandardCharsets.UTF_8.toString())); } catch (UnsupportedEncodingException var5) { this.logger.error(var5.getMessage(), var5); } if (i < cookieTokens.length - 1) { sb.append(":"); } }
1.2.3、token的解析
查看 RememberMeAuthenticationFilter
过滤器的 doFilter 方法
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
this.doFilter((HttpServletRequest)request, (HttpServletResponse)response, chain);
}
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
if (SecurityContextHolder.getContext().getAuthentication() != null) {
this.logger.debug(LogMessage.of(() -> {
return "SecurityContextHolder not populated with remember-me token, as it already contained: '" + SecurityContextHolder.getContext().getAuthentication() + "'";
}));
//
chain.doFilter(request, response);
} else {
// 如果从 SecurityContextHolder 中无法获取到当前登录用户实例,就调用 rememberMeServices.autoLogin方法进行自动登录逻辑。
// autoLogin 自动登录
Authentication rememberMeAuth = this.rememberMeServices.autoLogin(request, response);
if (rememberMeAuth != null) {
try {
rememberMeAuth = this.authenticationManager.authenticate(rememberMeAuth);
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(rememberMeAuth);
SecurityContextHolder.setContext(context);
this.onSuccessfulAuthentication(request, response, rememberMeAuth);
this.logger.debug(LogMessage.of(() -> {
return "SecurityContextHolder populated with remember-me token: '" + SecurityContextHolder.getContext().getAuthentication() + "'";
}));
this.securityContextRepository.saveContext(context, request, response);
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(SecurityContextHolder.getContext().getAuthentication(), this.getClass()));
}
if (this.successHandler != null) {
this.successHandler.onAuthenticationSuccess(request, response, rememberMeAuth);
return;
}
} catch (AuthenticationException var6) {
this.logger.debug(LogMessage.format("SecurityContextHolder not populated with remember-me token, as AuthenticationManager rejected Authentication returned by RememberMeServices: '%s'; invalidating remember-me token", rememberMeAuth), var6);
this.rememberMeServices.loginFail(request, response);
this.onUnsuccessfulAuthentication(request, response, var6);
}
}
chain.doFilter(request, response);
}
}
autoLogin方法:
public final Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) {
// 1.extractRememberMeCookie(): 提取出 cookie 信息,
String rememberMeCookie = this.extractRememberMeCookie(request);
if (rememberMeCookie == null) {
return null;
} else {
this.logger.debug("Remember-me cookie detected");
if (rememberMeCookie.length() == 0) {
this.logger.debug("Cookie was empty");
this.cancelCookie(request, response);
return null;
} else {
try {
// 2.decodeCookie():对cookie 信息进行解码
String[] cookieTokens = this.decodeCookie(rememberMeCookie);
// 3.UserDetails user:调用 processAutoLoginCookie方法校验token,
UserDetails user = this.processAutoLoginCookie(cookieTokens, request, response);
// 4.userDetailsChecker.check(user):用户状态判断
this.userDetailsChecker.check(user);
this.logger.debug("Remember-me cookie accepted");
创建 RememberMeAuthenticationToken实例
// 5.createSuccessfulAuthentication():创建 RememberMeAuthenticationToken实例
return this.createSuccessfulAuthentication(request, user);
} catch (CookieTheftException var6) {
this.cancelCookie(request, response);
throw var6;
} catch (UsernameNotFoundException var7) {
this.logger.debug("Remember-me login was valid but corresponding user not found.", var7);
} catch (InvalidCookieException var8) {
this.logger.debug("Invalid remember-me cookie: " + var8.getMessage());
} catch (AccountStatusException var9) {
this.logger.debug("Invalid UserDetails: " + var9.getMessage());
} catch (RememberMeAuthenticationException var10) {
this.logger.debug(var10.getMessage());
}
this.cancelCookie(request, response);
return null;
}
}
}
autologin 逻辑如下:
- 提取出 cookie 信息
- 对 cookie 信息进行解码
- 调用 processAutoLoginCookie方法校验token,核心流程:首先获取用户名和过期时间,再根据用户名查询到用户密码,然后通过 MD5 散列函数计算出散列值,再将拿到的散列值和浏览器传递来的散列值进行对比,就能确认这个令牌是否有效,进而确认登录是否有效。
- 用户状态判断
- 创建 RememberMeAuthenticationToken实例
2、将 token持久化到数据库
“记住我”功能将 token保存到浏览器中,很容易盗取,而且 cookie的值还与用户名、密码这些敏感数据相关,虽然加密了,但是将敏感信息存在客户端,还是不太安全。
回顾源码:AbstractRememberMeServices
public abstract class AbstractRememberMeServices implements RememberMeServices, InitializingBean, LogoutHandler, MessageSourceAware {
...
// 我们可以发现在 AbstractRememberMeServices 下,onLoginSuccess()是一个抽象方法,
protected abstract void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication);
}
查看实现该抽象方法的子类有哪些?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pdcw2vSi-1666963429682)(C:\Users\25712\AppData\Roaming\Typora\typora-user-images\image-20221028200842248.png)]
//1.
PersistentTokenBasedRememberMeServices extends AbstractRememberMeServices
//2.
TokenBasedRememberMeServices extends AbstractRememberMeServices
上面源码我们也看到了,认证成功之后,有两个“记住我”功能实现方式。
所以,Spring Security还提供了 remember me的另一种相对更安全的实现机制:将 token持久化到数据库。
原理:
在客户端的 cookie中,仅保存一个无意义的加密串(与用户名、密码等敏感数据无关),
然后在数据库中保存该加密串与用户信息的对应关系,
自动登录时,用cookie中的加密串,到数据库中验证,如果通过,自动登录才算通过。
下面来操作一番!
2.1、演示
1、创建记录 remember me一张表来记录令牌信息:
CREATE TABLE `persistent_logins` (
`username` varchar(64) NOT NULL,
`series` varchar(64) NOT NULL,
`token` varchar(64) NOT NULL,
`last_used` timestamp NOT NULL,
PRIMARY KEY (`series`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
注意:这张表的名称和字段都是固定的,不要修改,官方提供的。如果使用默认的 JDBC,即 JdbcTokenRepositoryImpl。
这张表我们也可以完全自定义,也可以使用系统提供的 JDBC 来操作。这里我们使用官方的表和默认的 JDBC来实现“记住我”功能。
2、代码实现
2.1、后端
在 Spring Security 配置类中开启它即可。并配置默认的 JDBC,指定 JdbcTokenRepositoryImpl。
// 1.指定数据源
@Autowired
private DataSource dataSource;
@Bean
JdbcTokenRepositoryImpl jdbcTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
return jdbcTokenRepository;
}
// 2. SpringSecurity配置相关信息
@Override
public void configure(HttpSecurity http) throws Exception {
// 释放静态资源,指定拦截规则,指定自定义的认证和退出页面,csrf配置等
http.authorizeRequests()
// 指定拦截规则
...
// 开启记住我功能
.rememberMe()
.key("rememberMeKey") // 默认 key为UUID,我们可以自定义 key
.tokenValiditySeconds(60) //设置token的过期时间,默认2周
.tokenRepository(jdbcTokenRepository())
;
}
2.2、前端
搬前面的页面即可!
3、测试
前端同上,登录认证通过后,关掉浏览器,再次打开页面,remember-me功能生效了,数据表多了一条记录。
可以看到,cookie 中 remember-me 的使用用 : 隔开,分成了两部分:
- 数据表中的 series字段值。
- 数据表中的 token字段值。
2.2、源码分析
分析过程同上,重点关注 PersistentTokenBasedRememberMeServices类
。
2.2.1、表记录生成
查看 PersistentTokenBasedRememberMeServices类的 loginSuccess方法。
protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
// 获取用户名
String username = successfulAuthentication.getName();
this.logger.debug("Creating new persistent login for user " + username);
//1.生成 series和token
PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(username, this.generateSeriesData(), this.generateTokenData(), new Date());
try {
//2.入库
this.tokenRepository.createNewToken(persistentToken);
//3.添加token到浏览器的Cookie中
this.addCookie(persistentToken, request, response);
} catch (Exception var7) {
this.logger.error("Failed to save persistent token ", var7);
}
}
loginSuccess 逻辑如下:
- 构造一个 PersistentRememberMeToken 实例,generateSeriesData 和 generateTokenData 方法分别用来获取 series 和 token,具体的生成过程实际上就是调用 SecureRandom 生成随机数再进行 Base64 编码,不同于 Math.random 或者 java.util.Random 这种伪随机数,SecureRandom 则采用的是类似于密码学的随机数生成规则,其输出结果较难预测,适合在登录这样的场景下使用。
- 调用 tokenRepository 实例 (我们配置的 **JdbcTokenRepositoryImpl **) 中的 createNewToken 方法,将PersistentRememberMeToken 存入数据库中。
- 最后添加 series 和 token 到浏览器 Cookie中。
问题解决:
疑问1:PersistentRememberMeToken ?
public class PersistentRememberMeToken {
private final String username;
private final String series;
private final String tokenValue;
private final Date date;
...
}
疑问2:tokenRepository 哪来的?
public class PersistentTokenBasedRememberMeServices extends AbstractRememberMeServices {
private PersistentTokenRepository tokenRepository = new InMemoryTokenRepositoryImpl();
...
}
疑问3:createNewToken方法?
tokenRepository.createNewToken
public class InMemoryTokenRepositoryImpl implements PersistentTokenRepository {
...
public synchronized void createNewToken(PersistentRememberMeToken token) {
PersistentRememberMeToken current = (PersistentRememberMeToken)this.seriesTokens.get(token.getSeries());
if (current != null) {
throw new DataIntegrityViolationException("Series Id '" + token.getSeries() + "' already exists!");
} else {
this.seriesTokens.put(token.getSeries(), token);
}
}
疑问4:addCooki方法?
private void addCookie(PersistentRememberMeToken token, HttpServletRequest request, HttpServletResponse response) {
this.setCookie(new String[]{token.getSeries(), token.getTokenValue()}, this.getTokenValiditySeconds(), request, response);
}
疑问5:jdbcToken?
public class JdbcTokenRepositoryImpl extends JdbcDaoSupport implements PersistentTokenRepository {
public static final String CREATE_TABLE_SQL = "create table persistent_logins (username varchar(64) not null, series varchar(64) primary key, token varchar(64) not null, last_used timestamp not null)";
public static final String DEF_TOKEN_BY_SERIES_SQL = "select username,series,token,last_used from persistent_logins where series = ?";
public static final String DEF_INSERT_TOKEN_SQL = "insert into persistent_logins (username, series, token, last_used) values(?,?,?,?)";
public static final String DEF_UPDATE_TOKEN_SQL = "update persistent_logins set token = ?, last_used = ? where series = ?";
public static final String DEF_REMOVE_USER_TOKENS_SQL = "delete from persistent_logins where username = ?";
private String tokensBySeriesSql = "select username,series,token,last_used from persistent_logins where series = ?";
private String insertTokenSql = "insert into persistent_logins (username, series, token, last_used) values(?,?,?,?)";
private String updateTokenSql = "update persistent_logins set token = ?, last_used = ? where series = ?";
private String removeUserTokensSql = "delete from persistent_logins where username = ?";
private boolean createTableOnStartup;
...
2.2.2、token解析
1、查看 rememberMeServices.autoLogin方法,寻找进行自动登录逻辑的方法。
public final Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) {
String rememberMeCookie = this.extractRememberMeCookie(request);
if (rememberMeCookie == null) {
return null;
} else {
this.logger.debug("Remember-me cookie detected");
if (rememberMeCookie.length() == 0) {
this.logger.debug("Cookie was empty");
this.cancelCookie(request, response);
return null;
} else {
try {
String[] cookieTokens = this.decodeCookie(rememberMeCookie);
// 实现自动登录,两个都重写了这个方法!
UserDetails user = this.processAutoLoginCookie(cookieTokens, request, response);
this.userDetailsChecker.check(user);
this.logger.debug("Remember-me cookie accepted");
return this.createSuccessfulAuthentication(request, user);
} catch (CookieTheftException var6) {
this.cancelCookie(request, response);
throw var6;
} catch (UsernameNotFoundException var7) {
this.logger.debug("Remember-me login was valid but corresponding user not found.", var7);
} catch (InvalidCookieException var8) {
this.logger.debug("Invalid remember-me cookie: " + var8.getMessage());
} catch (AccountStatusException var9) {
this.logger.debug("Invalid UserDetails: " + var9.getMessage());
} catch (RememberMeAuthenticationException var10) {
this.logger.debug(var10.getMessage());
}
this.cancelCookie(request, response);
return null;
}
}
}
2、找到实现自动登录的方法 processAutoLoginCookie 方法,源码如下
protected abstract UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request, HttpServletResponse response) throws RememberMeAuthenticationException, UsernameNotFoundException;
查看哪些类实现了该方法,发现两个 “记住我” 都重写了该方法!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sMihQMTj-1666963429683)(C:\Users\25712\AppData\Roaming\Typora\typora-user-images\image-20221028205203197.png)]
3、查看 PersistentTokenBasedRememberMeServices类的 processAutoLoginCookie方法。
源码如下:
protected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request, HttpServletResponse response) {
if (cookieTokens.length != 2) {
throw new InvalidCookieException("Cookie token did not contain 2 tokens, but contained '" + Arrays.asList(cookieTokens) + "'");
} else {
String presentedSeries = cookieTokens[0];
String presentedToken = cookieTokens[1];
// 1.getTokenForSeries() 查库
PersistentRememberMeToken token = this.tokenRepository.getTokenForSeries(presentedSeries);
if (token == null) {
throw new RememberMeAuthenticationException("No persistent token found for series id: " + presentedSeries);
// 2.equals() 比较
} else if (!presentedToken.equals(token.getTokenValue())) {
this.tokenRepository.removeUserTokens(token.getUsername());
throw new CookieTheftException(this.messages.getMessage("PersistentTokenBasedRememberMeServices.cookieStolen", "Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack."));
// 3.getTokenValiditySeconds()
} else if (token.getDate().getTime() + (long)this.getTokenValiditySeconds() * 1000L < System.currentTimeMillis()) {
throw new RememberMeAuthenticationException("Remember-me login has expired");
} else {
this.logger.debug(LogMessage.format("Refreshing persistent login token for user '%s', series '%s'", token.getUsername(), token.getSeries()));
PersistentRememberMeToken newToken = new PersistentRememberMeToken(token.getUsername(), token.getSeries(), this.generateTokenData(), new Date());
try {
// 4.updateToken() 更新过期时间
this.tokenRepository.updateToken(newToken.getSeries(), newToken.getTokenValue(), newToken.getDate());
// 5.addCookie()
this.addCookie(newToken, request, response);
} catch (Exception var9) {
this.logger.error("Failed to update token: ", var9);
throw new RememberMeAuthenticationException("Autologin failed due to data access problem");
}
// 6.loadUserByUsername()
return this.getUserDetailsService().loadUserByUsername(token.getUsername());
}
}
}
processAutoLoginCookie的逻辑如下:
- 从前端传来的 cookie 中解析出 series 和 token。
- 根据 series 从数据库中查询出一个 PersistentRememberMeToken 实例。
- 如果查出来的 token 和前端传来的 token 不相同,此时根据用户名移除相关的 token,相当于必须要重新输入用户名密码登录才能获
- 取新的自动登录权限。
- 校验 token 是否过期。
- 构造新的 PersistentRememberMeToken 对象,并且更新数据库中的 token信息。
- 将新的令牌重新添加到 cookie 中返回。
- 根据用户名查询用户信息,再走一波登录流程。