首頁  >  文章  >  Java  >  SpringBoot Security如何實現單點登出並清除所有token

SpringBoot Security如何實現單點登出並清除所有token

WBOY
WBOY轉載
2023-05-14 13:43:111997瀏覽

需求

  • 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

  • #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)
            ;
        }
    	...
    }

    清除

  • 繼承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中文網其他相關文章!

陳述:
本文轉載於:yisu.com。如有侵權,請聯絡admin@php.cn刪除