Spring Security Remember me使用及原理详解

作者:仅此而已-远方 时间:2023-10-28 04:59:56 

Remember me功能就是勾选"记住我"后,一次登录,后面在有效期内免登录。

先看具体配置:

pom文件:


<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>

Security的配置:


@Autowired
 private UserDetailsService myUserDetailServiceImpl; // 用户信息服务
 @Autowired
 private DataSource dataSource; // 数据源
 @Override
 protected void configure(HttpSecurity http) throws Exception {
   // formLogin()是默认的登录表单页,如果不配置 loginPage(url),则使用 spring security
   // 默认的登录页,如果配置了 loginPage()则使用自定义的登录页
   http.formLogin() // 表单登录
       .loginPage(SecurityConst.AUTH_REQUIRE)
       .loginProcessingUrl(SecurityConst.AUTH_FORM) // 登录请求拦截的url,也就是form表单提交时指定的action
       .successHandler(loginSuccessHandler)
       .failureHandler(loginFailureHandler)
       .and()
     .rememberMe()
       .userDetailsService(myUserDetailServiceImpl) // 设置userDetailsService
       .tokenRepository(persistentTokenRepository()) // 设置数据访问层
       .tokenValiditySeconds(60 * 60) // 记住我的时间(秒)
       .and()
     .authorizeRequests() // 对请求授权
       .antMatchers(SecurityConst.AUTH_REQUIRE, securityProperty.getBrowser().getLoginPage()).permitAll() // 允许所有人访问login.html和自定义的登录页
       .anyRequest() // 任何请求
       .authenticated()// 需要身份认证
       .and()
     .csrf().disable() // 关闭跨站伪造
   ;
 }
 /**
  * 持久化token
  *
  * Security中,默认是使用PersistentTokenRepository的子类InMemoryTokenRepositoryImpl,将token放在内存中
  * 如果使用JdbcTokenRepositoryImpl,会创建表persistent_logins,将token持久化到数据库
  */
 @Bean
 public PersistentTokenRepository persistentTokenRepository() {
   JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
   tokenRepository.setDataSource(dataSource); // 设置数据源
//    tokenRepository.setCreateTableOnStartup(true); // 启动创建表,创建成功后注释掉
   return tokenRepository;
 }

上面的myUserDetailServiceImpl是自己实现的UserDetailsService接口,dataSource会自动读取数据库配置。过期时间设置的3600秒,即一个小时

在登录页面加一行(name必须是remeber-me):

"记住我"基本原理:

Spring Security Remember me使用及原理详解

1、第一次发送认证请求,会被UsernamePasswordAuthenticationFilter拦截,然后身份认证。

认证成功后,在AbstracAuthenticationProcessingFilter中,有个RememberMeServices接口。

该接口默认实现类是NullRememberMeServices,这里会调用另一个实现抽象类AbstractRememberMeServices


// ...
 private RememberMeServices rememberMeServices = new NullRememberMeServices();
 protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
     Authentication authResult) throws IOException, ServletException {
   // ...
   SecurityContextHolder.getContext().setAuthentication(authResult);
   // 登录成功后,调用RememberMeServices保存Token相关信息
   rememberMeServices.loginSuccess(request, response, authResult);
   // ...
 }

2、调用AbstractRememberMeServices的loginSuccess方法。

可以看到如果request中name为"remember-me"为true时,才会调用下面的onLoginSuccess()方法。这也是为什么上面登录页中的表单,name必须是"remember-me"的原因:

Spring Security Remember me使用及原理详解

3、在Security中配置了rememberMe()之后, 会由PersistentTokenBasedRememberMeServices去实现父类AbstractRememberMeServices中的抽象方法。

在PersistentTokenBasedRememberMeServices中,有一个PersistentTokenRepository,会生成一个Token,并将这个Token写到cookie里面返回浏览器。PersistentTokenRepository的默认实现类是InMemoryTokenRepositoryImpl,该默认实现类会将token保存到内存中。这里我们配置了它的另一个实现类JdbcTokenRepositoryImpl,该类会将Token持久化到数据库中


// ...

private PersistentTokenRepository tokenRepository = new InMemoryTokenRepositoryImpl();
 protected void onLoginSuccess(HttpServletRequest request,
     HttpServletResponse response, Authentication successfulAuthentication) {
   String username = successfulAuthentication.getName();

logger.debug("Creating new persistent login for user " + username);

// 创建一个PersistentRememberMeToken
   PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(
       username, generateSeriesData(), generateTokenData(), new Date());
   try {
     // 保存Token
     tokenRepository.createNewToken(persistentToken);
     // 将Token写到Cookie中
     addCookie(persistentToken, request, response);
   }
   catch (Exception e) {
     logger.error("Failed to save persistent token ", e);
   }
 }

4、JdbcTokenRepositoryImpl将Token持久化到数据库


/** The default SQL used by <tt>createNewToken</tt> */
 public static final String DEF_INSERT_TOKEN_SQL = "insert into persistent_logins (username, series, token, last_used) values(?,?,?,?)";

public void createNewToken(PersistentRememberMeToken token) {
   getJdbcTemplate().update(insertTokenSql, token.getUsername(), token.getSeries(),
       token.getTokenValue(), token.getDate());
 }

查看数据库,可以看到往persistent_logins 中插入了一条数据:

Spring Security Remember me使用及原理详解

5、重启服务,发送第二次认证请求,只会携带Cookie。

所以直接会被RememberMeAuthenticationFilter拦截,并且此时内存中没有认证信息。

可以看到,此时的RememberMeServices是由PersistentTokenBasedRememberMeServices实现

Spring Security Remember me使用及原理详解

6、在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) + "'");
   }

// 从Cookie中获取Series和Token
   final String presentedSeries = cookieTokens[0];
   final String presentedToken = cookieTokens[1];

//在数据库中,通过Series查询PersistentRememberMeToken
   PersistentRememberMeToken token = tokenRepository
       .getTokenForSeries(presentedSeries);

if (token == null) {
     throw new RememberMeAuthenticationException(
         "No persistent token found for series id: " + presentedSeries);
   }

// 校验数据库中Token和Cookie中的Token是否相同
   if (!presentedToken.equals(token.getTokenValue())) {
     tokenRepository.removeUserTokens(token.getUsername());

throw new CookieTheftException(
         messages.getMessage(
             "PersistentTokenBasedRememberMeServices.cookieStolen",
             "Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack."));
   }

// 判断Token是否超时
   if (token.getDate().getTime() + getTokenValiditySeconds() * 1000L < System
       .currentTimeMillis()) {
     throw new RememberMeAuthenticationException("Remember-me login has expired");
   }

if (logger.isDebugEnabled()) {
     logger.debug("Refreshing persistent login token for user '"
         + token.getUsername() + "', series '" + token.getSeries() + "'");
   }

// 创建一个新的PersistentRememberMeToken
   PersistentRememberMeToken newToken = new PersistentRememberMeToken(
       token.getUsername(), token.getSeries(), generateTokenData(), new Date());

try {
     //更新数据库中Token
     tokenRepository.updateToken(newToken.getSeries(), newToken.getTokenValue(),
         newToken.getDate());
     //重新写到Cookie
     addCookie(newToken, request, response);
   }
   catch (Exception e) {
     logger.error("Failed to update token: ", e);
     throw new RememberMeAuthenticationException(
         "Autologin failed due to data access problem");
   }
   //调用UserDetailsService获取用户信息
   return getUserDetailsService().loadUserByUsername(token.getUsername());
 }

7、获取用户相关信息后,再调用AuthenticationManager去认证授权

来源:https://www.cnblogs.com/xuwenjin/p/9933218.html

标签:spring,security,remember,me
0
投稿

猜你喜欢

  • 使用SpringBoot实现微服务超时重试模式的示例

    2021-12-28 13:58:45
  • Java的StringBuilder在高性能场景下的正确用法

    2021-11-23 17:57:01
  • Java版的7种单例模式写法示例

    2023-08-13 04:24:57
  • 微信开发准备第二步 springmvc mybatis项目结构搭建

    2021-07-16 22:18:37
  • Unity学习之FSM有限状态机

    2022-09-10 06:18:33
  • dialog dismiss时键盘不消失的问题浅析及解决办法

    2021-09-18 10:06:30
  • 教你怎么用java一键自动生成数据库文档

    2021-08-01 02:34:36
  • 基于idea 的 Java中的get/set方法之优雅的写法

    2023-11-26 20:22:50
  • Flask实现异步非阻塞请求功能实例解析

    2022-02-03 14:03:55
  • java计算给定字符串中出现次数最多的字母和该字母出现次数的方法

    2022-01-13 03:00:04
  • 深入了解c# 迭代器和列举器

    2022-04-24 16:32:37
  • Spring Mvc下实现以文件流方式下载文件的方法示例

    2023-11-12 10:14:22
  • 基于Java中字符串indexof() 的使用方法

    2022-12-09 19:37:38
  • Android5.0新控件实例详解

    2022-12-06 21:58:18
  • C# 泛型集合类List<T>使用总结

    2021-08-30 02:04:01
  • 通过LinQ查询字符出现次数的实例方法

    2023-12-10 13:40:01
  • Swagger实现动态条件注入与全局拦截功能详细流程

    2023-11-23 13:41:05
  • Android 高仿微信语音聊天页面高斯模糊(毛玻璃效果)

    2021-09-27 18:56:46
  • SpringBoot+Vue.js实现前后端分离的文件上传功能

    2023-08-10 08:11:18
  • Java中Map集合的常用方法详解

    2021-12-31 16:05:54
  • asp之家 软件编程 m.aspxhome.com