关于Spring Secruity中“记住我”的理解

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 的使用用 : 隔开,分成了三部分:

  1. 用户名。
  2. 时间戳,即 token的过期时间。
  3. 是使用 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() 方法的逻辑如下:

  1. 获取用户信息

    从登录成功的 Authentication 中提取出用户名/密码。
    由于登录成功之后,密码可能被擦除了,所以,如果一开始没有拿到密码,就再从 UserDetailsService 中重新加载用户并重新获取密码。

  2. 获取token的有效期,令牌有效期默认就是两周。

  3. 生成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!");
        }
    }
    
  4. 将用户名、令牌有效期以及计算得到的散列值,,生成 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 逻辑如下:

  1. 提取出 cookie 信息
  2. 对 cookie 信息进行解码
  3. 调用 processAutoLoginCookie方法校验token,核心流程:首先获取用户名和过期时间,再根据用户名查询到用户密码,然后通过 MD5 散列函数计算出散列值,再将拿到的散列值和浏览器传递来的散列值进行对比,就能确认这个令牌是否有效,进而确认登录是否有效。
  4. 用户状态判断
  5. 创建 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 的使用用 : 隔开,分成了两部分:

  1. 数据表中的 series字段值。
  2. 数据表中的 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 逻辑如下:

  1. 构造一个 PersistentRememberMeToken 实例,generateSeriesDatagenerateTokenData 方法分别用来获取 series 和 token,具体的生成过程实际上就是调用 SecureRandom 生成随机数再进行 Base64 编码,不同于 Math.random 或者 java.util.Random 这种伪随机数,SecureRandom 则采用的是类似于密码学的随机数生成规则,其输出结果较难预测,适合在登录这样的场景下使用。
  2. 调用 tokenRepository 实例 (我们配置的 **JdbcTokenRepositoryImpl **) 中的 createNewToken 方法,将PersistentRememberMeToken 存入数据库中。
  3. 最后添加 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的逻辑如下:

  1. 从前端传来的 cookie 中解析出 series 和 token。
  2. 根据 series 从数据库中查询出一个 PersistentRememberMeToken 实例。
  3. 如果查出来的 token 和前端传来的 token 不相同,此时根据用户名移除相关的 token,相当于必须要重新输入用户名密码登录才能获
  4. 取新的自动登录权限。
  5. 校验 token 是否过期。
  6. 构造新的 PersistentRememberMeToken 对象,并且更新数据库中的 token信息。
  7. 将新的令牌重新添加到 cookie 中返回。
  8. 根据用户名查询用户信息,再走一波登录流程。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值