SpringBootSecurity实现单点登出并清除所有token

Olivia ·
更新时间:2024-09-20
· 640 次阅读

目录

需求

记录token

清除token

解决登出时长过长

需求

A、B、C 系统通过 sso 服务实现登录

A、B、C 系统分别获取 Atoken、Btoken、Ctoken 三个 token

其中某一个系统主动登出后,其他两个系统也登出

至此全部 Atoken、Btoken、Ctoken 失效

记录token

pom 文件引入依赖

Redis数据库依赖

hutool:用于解析token

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.13</version> </dependency>

token 存储类 实现 AuthJdbcTokenStore

TokenStore 继承 JdbcTokenStore

使用登录用户的用户名 username 做 Redis 的 key

因为用户登录的系统会有多个,所以 value 使用 Redis 的列表类型来存储 token

设置有效时间,保证不少于 list 里 token 的最大有效时间

@Component public class AuthJdbcTokenStore extends JdbcTokenStore { public static final String USER_HAVE_TOKEN = "user-tokens:"; @Resource RedisTemplate redisTemplate; public AuthJdbcTokenStore(DataSource connectionFactory) { super(connectionFactory); } @Override public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) { super.storeAccessToken(token, authentication); if (Optional.ofNullable(authentication.getUserAuthentication()).isPresent()) { User user = (User) authentication.getUserAuthentication().getPrincipal(); String userTokensKey = USER_HAVE_TOKEN + user.getUsername(); String tokenValue = token.getValue(); redisTemplate.opsForList().leftPush(userTokensKey, tokenValue); Long seconds = redisTemplate.opsForValue().getOperations().getExpire(userTokensKey); Long tokenExpTime = getExpTime(tokenValue); Long expTime = seconds < tokenExpTime ? tokenExpTime : seconds; redisTemplate.expire(userTokensKey, expTime, TimeUnit.SECONDS); } } private long getExpTime(String accessToken) { JWT jwt = JWTUtil.parseToken(accessToken); cn.hutool.json.JSONObject jsonObject = jwt.getPayload().getClaimsJson(); long nowTime = Instant.now().getEpochSecond(); long expEndTime = jsonObject.getLong("exp"); long expTime = (expEndTime - nowTime); return expTime; } }

oauth_access_token 使用 JdbcTokenStore 存储 token 需要新增表

CREATE TABLE `oauth_access_token` ( `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `token_id` varchar(255) DEFAULT NULL, `token` blob, `authentication_id` varchar(255) DEFAULT NULL, `user_name` varchar(255) DEFAULT NULL, `client_id` varchar(255) DEFAULT NULL, `authentication` blob, `refresh_token` varchar(255) DEFAULT NULL, UNIQUE KEY `authentication_id` (`authentication_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;

AuthorizationServerConfigurerAdapter 使用 AuthJdbcTokenStore 做 token 存储

引入 DataSource,因为 JdbcTokenStore 的构造方法必须传入 DataSource

创建按 TokenStore,用 AuthJdbcTokenStore 实现

tokenServices 添加 TokenStore

endpoints 添加 tokenServices

@Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private DataSource dataSource; ... @Bean public TokenStore tokenStore() { JdbcTokenStore tokenStore = new AuthJdbcTokenStore(dataSource); return tokenStore; } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) { DefaultTokenServices tokenServices = new DefaultTokenServices(); tokenServices.setTokenStore(tokenStore()); endpoints .authenticationManager(authenticationManager) .tokenServices(tokenServices) .accessTokenConverter(converter) ; } ... } 清除token

继承 SimpleUrlLogoutSuccessHandler

获取用户名 userName

获取登录时存储在 Redis 的 token 列表

token 字符串转换成 OAuth2AccessToken

使用 tokenStore 删除 token

@Component public class AuthLogoutSuccessHandler1 extends SimpleUrlLogoutSuccessHandler { String USER_HAVE_TOKEN = AuthJdbcTokenStore.USER_HAVE_TOKEN; @Resource RedisTemplate redisTemplate; @Resource TokenStore tokenStore; @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { if (!Objects.isNull(authentication)) { String userName = authentication.getName(); String userTokensKey = USER_HAVE_TOKEN + userName; Long size = redisTemplate.opsForList().size(userTokensKey); List<String> list = redisTemplate.opsForList().range(userTokensKey, 0, size); for (String tokenValue : list) { OAuth2AccessToken token = tokenStore.readAccessToken(tokenValue); if (Objects.nonNull(token)) { tokenStore.removeAccessToken(token); } } redisTemplate.delete(userTokensKey); super.handle(request, response, authentication); } } } 解决登出时长过长

场景:项目运行一段时间后,发现登出时间越来越慢

问题:通过 debug 发现耗时主要在删除 token 那一段

tokenStore.removeAccessToken(token);

原因:随着时间推移,token 越来越多,token 存储表 oauth_access_token 变得异常的大,所以删除效率非常差

解决办法:使用其他 TokenStore,或者清除 oauth_access_token 的表数据

到此这篇关于SpringBoot Security实现单点登出并清除所有token的文章就介绍到这了,更多相关SpringBoot Security单点登出内容请搜索软件开发网以前的文章或继续浏览下面的相关文章希望大家以后多多支持软件开发网!



token

需要 登录 后方可回复, 如果你还没有账号请 注册新账号