经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Spring » 查看文章
SpringBoot Security实现单点登出并清除所有token
来源:jb51  时间:2023/1/16 9:16:24  对本文有异议

需求

  • A、B、C 系统通过 sso 服务实现登录
  • A、B、C 系统分别获取 Atoken、Btoken、Ctoken 三个 token
  • 其中某一个系统主动登出后,其他两个系统也登出
  • 至此全部 Atoken、Btoken、Ctoken 失效

记录token

pom 文件引入依赖

  • Redis数据库依赖
  • hutool:用于解析token
  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-redis</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>cn.hutool</groupId>
  7. <artifactId>hutool-all</artifactId>
  8. <version>5.7.13</version>
  9. </dependency>

token 存储类 实现 AuthJdbcTokenStore

  • TokenStore 继承 JdbcTokenStore
  • 使用登录用户的用户名 username 做 Redis 的 key
  • 因为用户登录的系统会有多个,所以 value 使用 Redis 的列表类型来存储 token
  • 设置有效时间,保证不少于 list 里 token 的最大有效时间
  1. @Component
  2. public class AuthJdbcTokenStore extends JdbcTokenStore {
  3. public static final String USER_HAVE_TOKEN = "user-tokens:";
  4. @Resource
  5. RedisTemplate redisTemplate;
  6. public AuthJdbcTokenStore(DataSource connectionFactory) {
  7. super(connectionFactory);
  8. }
  9. @Override
  10. public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
  11. super.storeAccessToken(token, authentication);
  12. if (Optional.ofNullable(authentication.getUserAuthentication()).isPresent()) {
  13. User user = (User) authentication.getUserAuthentication().getPrincipal();
  14. String userTokensKey = USER_HAVE_TOKEN + user.getUsername();
  15. String tokenValue = token.getValue();
  16. redisTemplate.opsForList().leftPush(userTokensKey, tokenValue);
  17. Long seconds = redisTemplate.opsForValue().getOperations().getExpire(userTokensKey);
  18. Long tokenExpTime = getExpTime(tokenValue);
  19. Long expTime = seconds < tokenExpTime ? tokenExpTime : seconds;
  20. redisTemplate.expire(userTokensKey, expTime, TimeUnit.SECONDS);
  21. }
  22. }
  23. private long getExpTime(String accessToken) {
  24. JWT jwt = JWTUtil.parseToken(accessToken);
  25. cn.hutool.json.JSONObject jsonObject = jwt.getPayload().getClaimsJson();
  26. long nowTime = Instant.now().getEpochSecond();
  27. long expEndTime = jsonObject.getLong("exp");
  28. long expTime = (expEndTime - nowTime);
  29. return expTime;
  30. }
  31. }

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

  1. CREATE TABLE `oauth_access_token` (
  2. `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  3. `token_id` varchar(255) DEFAULT NULL,
  4. `token` blob,
  5. `authentication_id` varchar(255) DEFAULT NULL,
  6. `user_name` varchar(255) DEFAULT NULL,
  7. `client_id` varchar(255) DEFAULT NULL,
  8. `authentication` blob,
  9. `refresh_token` varchar(255) DEFAULT NULL,
  10. UNIQUE KEY `authentication_id` (`authentication_id`)
  11. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;

AuthorizationServerConfigurerAdapter 使用 AuthJdbcTokenStore 做 token 存储

  • 引入 DataSource,因为 JdbcTokenStore 的构造方法必须传入 DataSource
  • 创建按 TokenStore,用 AuthJdbcTokenStore 实现
  • tokenServices 添加 TokenStore
  • endpoints 添加 tokenServices
  1. @Configuration
  2. @EnableAuthorizationServer
  3. public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
  4. @Autowired
  5. private DataSource dataSource;
  6. ...
  7. @Bean
  8. public TokenStore tokenStore() {
  9. JdbcTokenStore tokenStore = new AuthJdbcTokenStore(dataSource);
  10. return tokenStore;
  11. }
  12. @Override
  13. public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
  14. DefaultTokenServices tokenServices = new DefaultTokenServices();
  15. tokenServices.setTokenStore(tokenStore());
  16. endpoints
  17. .authenticationManager(authenticationManager)
  18. .tokenServices(tokenServices)
  19. .accessTokenConverter(converter)
  20. ;
  21. }
  22. ...
  23. }

清除token

  • 继承 SimpleUrlLogoutSuccessHandler
  • 获取用户名 userName
  • 获取登录时存储在 Redis 的 token 列表
  • token 字符串转换成 OAuth2AccessToken
  • 使用 tokenStore 删除 token
  1. @Component
  2. public class AuthLogoutSuccessHandler1 extends SimpleUrlLogoutSuccessHandler {
  3. String USER_HAVE_TOKEN = AuthJdbcTokenStore.USER_HAVE_TOKEN;
  4. @Resource
  5. RedisTemplate redisTemplate;
  6. @Resource
  7. TokenStore tokenStore;
  8. @Override
  9. public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
  10. if (!Objects.isNull(authentication)) {
  11. String userName = authentication.getName();
  12. String userTokensKey = USER_HAVE_TOKEN + userName;
  13. Long size = redisTemplate.opsForList().size(userTokensKey);
  14. List<String> list = redisTemplate.opsForList().range(userTokensKey, 0, size);
  15. for (String tokenValue : list) {
  16. OAuth2AccessToken token = tokenStore.readAccessToken(tokenValue);
  17. if (Objects.nonNull(token)) {
  18. tokenStore.removeAccessToken(token);
  19. }
  20. }
  21. redisTemplate.delete(userTokensKey);
  22. super.handle(request, response, authentication);
  23. }
  24. }
  25. }

解决登出时长过长

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

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

  1. tokenStore.removeAccessToken(token);

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

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

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

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号