问题描述
使用过程中关于timeout和activity-timeout的设置碰到一点问题。
- 会话突然失效
- redis中无效token永不删除
- 失效的token不能及时删除
期望场景
用户在超时时间内,有操作时自动续期,超时后自动清除会话,基本和sa-token的临时有效期行为一致,不需要长期有效期到期时,强制退出,不允许并发登录。
第一反应是使用如下配置:
# token有效期,单位s 默认30天, -1代表永不过期
timeout: -1
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
activity-timeout: 5
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
is-concurrent: false
问题现象
超时重新登录后,原token并没有清除,还保留在redis中,并且无失效日期。
如果timeout=-1,所有的失效会话的token虽然都不能用了,但是会永远存在redis中。
如果给timeout设置一个值,虽然能解决redis中无效数据的问题,但是会碰到会话突然失效的问题,如使用如下配置:
# token有效期,单位s 默认30天, -1代表永不过期
timeout: 10
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
activity-timeout: 5
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
is-concurrent: false
登录后一直操作,虽然临时有效期会自动续期,但是长期有效期到期时,即使上一秒还在操作,下一秒也会强制清除会话,这样用户体验就会很差
解决思路
使用timeout设置会话有效期,禁用临时有效期,手动为长期有效期续期。
具体配置和代码
# token有效期,单位s 默认30天, -1代表永不过期
timeout: 2592000
# token临时有效期 ,永不过期
activity-timeout: -1
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
is-concurrent: false
autoRenew: false
注册拦截器的时候,每次校验都增加长期有效期的期限
@Value("${sa-token.timeout}")
private long tokenTimeout;
@Override
public void addInterceptors(InterceptorRegistry registry) {
log.info("排除路径:{}",excludePathList);
registry.addInterceptor(new SaInterceptor(handle -> {
StpUtil.checkLogin();
//续约长期有效期,避免redis中大量无效的token
StpUtil.renewTimeout(tokenTimeout);
}))
.addPathPatterns("/**")
.excludePathPatterns(excludePathList);
}
新的问题
因为不允许并发登录,用户重复登录的时候,会生成新的token,旧的token会失效。场景中设置的token有效期是30天,虽然到旧的无效token到期后会自动删除,但是要等30天才会删除。
解决办法
使用监听器监听replac事件,token被顶替的时候,直接注销token。
@Component
public class DgbSaTokenListener extends SaTokenListenerForSimple {
/**
* 被顶下线时,删除对应的token
* @param loginType 账号类别
* @param loginId 账号id
* @param tokenValue token值
*/
@Override
public void doReplaced(String loginType, Object loginId, String tokenValue) {
log.info("token被顶替,注销token");
StpUtil.logoutByTokenValue(tokenValue);
}
}
总结
- 避免使用timeout=-1的配置,会导致无效token永不删除
- 禁用临时有效期,为timeout设置实际的值,保证无效token到期会删除
- 校验登录时,手动为timeout续期,避免会话突然失效
- 在监听器中监听replaced事件,及时清除无效的token
感谢
sa-token是一个功能很完备的鉴权框架,使用也很简单,感谢作者提供这么好用的框架。