A、B、C 系統透過sso 服務實作登入
A、B、C 系統分別取得Atoken、Btoken 、Ctoken 三個token
其中某一個系統主動登出後,其他兩個系統也登出
至此全部Atoken、Btoken 、Ctoken 失效
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; } }
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
@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
@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中文網其他相關文章!