搜索
首页Javajava教程SpringBoot Security如何实现单点登出并清除所有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(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中文网其他相关文章!

声明
本文转载于:亿速云。如有侵权,请联系admin@php.cn删除

热AI工具

Undresser.AI Undress

Undresser.AI Undress

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

AI Clothes Remover

AI Clothes Remover

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

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热工具

MinGW - 适用于 Windows 的极简 GNU

MinGW - 适用于 Windows 的极简 GNU

这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

EditPlus 中文破解版

EditPlus 中文破解版

体积小,语法高亮,不支持代码提示功能

Atom编辑器mac版下载

Atom编辑器mac版下载

最流行的的开源编辑器

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境