使用SpringSecurity 的RememberMe功能遇到的坑

295 阅读3分钟

前言

SpringSecurity自带了RemeberMe的功能,后端只需简单配置即可实现,网上的教程千篇一律,这篇只简单介绍下背景,并说说我遇到的问题和解决方法

配置

remember-me默认配置

配置1.png 其实就是加一句代码,rememberMe()方法,则自动开启,即可实现功能,但是使用默认的存在一些问题:

  • 1.默认的的rememberMe功能,前端无法自定义名称必须传入remember-me值(一般是input checkbox,传入后台值是on),过滤器才生效,返回的cookie也是固定名称remember-me-cookie
  • 2.默认自动登录时间为14天,无法灵活配置
  • 3.默认使用的InMemoryTokenRepositoryImpl的存储方式(即内存),当服务宕机或者重启后,session失效,那么前端传入的token无法校验导致自动登录失效,体验上有问题

自定义jdbc配置

为了解决上面问题,需要一些特殊配置,首先是需要自定义传值名称和cookie名称,以及设置自定义时间

配置2.png 加入这三句代码即可配置,可将属性值改成自定义,我配置的2天过期失效。 修改持久化方法,改为数据库存储,SpringSecurity提供了默认的sql存储,包括增删改,需要导入默认的table:persistent_logins,有方法自动建表无需手动导入

@Autowired
private DataSource dataSource;
@Bean  
public PersistentTokenRepository persistentTokenRepository() {  
    JdbcTokenRepositoryImpl persistentTokenRepository = new JdbcTokenRepositoryImpl();  
    persistentTokenRepository.setDataSource(dataSource);  
   //自动建表,建完注释掉  
//     persistentTokenRepository.setCreateTableOnStartup(true);
    return persistentTokenRepository;  
}

dataSource为你在properties或者yaml中自定义的数据源,可以断点看下是否引入成功,或者直接看自动建表是否成功,成功说明dataSource引入没问题

遇到的问题

存储失败

当测试remember-me的功能时,发现并没有设置成功,persistent_logins表中也没有存对应的token,通过debug源码发现,登录成功后本应该走到PersistentTokenBasedRememberMeServices的onLoginSuccess的方法上,结果并没有进入,Google后在stack overflow提到同样问题。

问题1.png

经过源码排查发现,这个方法在入口在PersistentTokenBasedRememberMeServices.java中

Pasted image 20240814101437.png 而方法的调用在springsecurity内置的filter中,如果想只通过配置就能实现remember-me功能,你的filter需要实现UsernamePasswordAuthenticationFilter或者使用httpBasic()走BasicAuthenticationFilter 自带的过滤器才行!

Pasted3.png 由于我已经自定义了过滤器不想再去重复开发,决定手动引入这个方法,具体操作如下:

  • 1.注入PersistentTokenRepository,配置dataSource连接池,上面代码已写
  • 2.注入PersistentTokenBasedRememberMeServices和UserDetailsService
@Bean  
public PersistentTokenBasedRememberMeServices getPersistentTokenBasedRememberMeServices() {  
    PersistentTokenBasedRememberMeServices persistenceTokenBasedService = new PersistentTokenBasedRememberMeServices(  
          "rememberMe", userDetailsService(), persistentTokenRepository());  
    persistenceTokenBasedService.setAlwaysRemember(true);  
    return persistenceTokenBasedService;  
}  
  
@Override  
protected UserDetailsService userDetailsService() {  
    return userDetailsService;  
}
  • 3.这样你就可以在你登录的方法中调用loginSuccess方法了
private final PersistentTokenBasedRememberMeServices rememberMeServices;

@ApiOperation("登录授权")  
@RequestMapping(value = "/login", method = RequestMethod.POST)  
public Result<Object> login(@Validated @RequestBody AuthUserDTO authUser, HttpServletRequest request, HttpServletResponse response) {
	// ```登录认证略
	Authentication auth =SecurityContextHolder.getContext().getAuthentication();     if (auth != null && StringUtils.isNotEmpty(authUser.getRememberMe())) { 
		//调用 
        persistentTokenBasedRememberMeServices.loginSuccess(request, response,     auth);  
    }
    return Result.success(authInfo);
}
  • 4.用户退出登录,记得清理session
@ApiOperation("退出登录")  
@RequestMapping(value = "/logout", method = RequestMethod.DELETE)  
@Log("退出登录")  
public Result<Object> logout(HttpServletRequest request, HttpServletResponse response) {  
    //···退出逻辑略
     
    //清除session  
    Authentication auth = SecurityContextHolder.getContext().getAuthentication();  
    if (auth != null) {  
       persistentTokenBasedRememberMeServices.logout(request, response, auth);  
    }  
    return Result.success();  
}

使用apifox测试登录接口,发现数据库已有相关token信息,成功解决。

rrr814103249.png

Ps.为了矿石我也是拼辣!