需求
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(OAuth3AccessToken token, OAuth3Authentication 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 字符串转换成 OAuth3AccessToken
使用 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) { OAuth3AccessToken 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的详细内容。更多信息请关注PHP中文网其他相关文章!

本文讨论了使用Maven和Gradle进行Java项目管理,构建自动化和依赖性解决方案,以比较其方法和优化策略。

本文使用Maven和Gradle之类的工具讨论了具有适当的版本控制和依赖关系管理的自定义Java库(JAR文件)的创建和使用。

本文讨论了使用咖啡因和Guava缓存在Java中实施多层缓存以提高应用程序性能。它涵盖设置,集成和绩效优势,以及配置和驱逐政策管理最佳PRA

本文讨论了使用JPA进行对象相关映射,并具有高级功能,例如缓存和懒惰加载。它涵盖了设置,实体映射和优化性能的最佳实践,同时突出潜在的陷阱。[159个字符]

Java的类上载涉及使用带有引导,扩展程序和应用程序类负载器的分层系统加载,链接和初始化类。父代授权模型确保首先加载核心类别,从而影响自定义类LOA

本文解释了用于构建分布式应用程序的Java的远程方法调用(RMI)。 它详细介绍了接口定义,实现,注册表设置和客户端调用,以解决网络问题和安全性等挑战。

本文详细介绍了用于网络通信的Java的套接字API,涵盖了客户服务器设置,数据处理和关键考虑因素,例如资源管理,错误处理和安全性。 它还探索了性能优化技术,我

本文详细介绍了创建自定义Java网络协议。 它涵盖协议定义(数据结构,框架,错误处理,版本控制),实现(使用插座),数据序列化和最佳实践(效率,安全性,维护


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

EditPlus 中文破解版
体积小,语法高亮,不支持代码提示功能

SublimeText3 Linux新版
SublimeText3 Linux最新版

WebStorm Mac版
好用的JavaScript开发工具

禅工作室 13.0.1
功能强大的PHP集成开发环境

Atom编辑器mac版下载
最流行的的开源编辑器