需求
- 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单点登出内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!