亲宝软件园·资讯

展开

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

我有一只肥螳螂 人气:0

需求

记录token

pom 文件引入依赖

<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

@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 存储

@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

@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 的表数据

加载全部内容

相关教程
猜你喜欢
用户评论