SpringBoot Security實(shí)現(xiàn)單點(diǎn)登出并清除所有token
需求
- A、B、C 系統(tǒng)通過 sso 服務(wù)實(shí)現(xiàn)登錄
- A、B、C 系統(tǒng)分別獲取 Atoken、Btoken、Ctoken 三個(gè) token
- 其中某一個(gè)系統(tǒng)主動(dòng)登出后,其他兩個(gè)系統(tǒng)也登出
- 至此全部 Atoken、Btoken、Ctoken 失效
記錄token
pom 文件引入依賴
- Redis數(shù)據(jù)庫依賴
- 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 存儲(chǔ)類 實(shí)現(xiàn) AuthJdbcTokenStore
- TokenStore 繼承 JdbcTokenStore
- 使用登錄用戶的用戶名 username 做 Redis 的 key
- 因?yàn)橛脩舻卿浀南到y(tǒng)會(huì)有多個(gè),所以 value 使用 Redis 的列表類型來存儲(chǔ) token
- 設(shè)置有效時(shí)間,保證不少于 list 里 token 的最大有效時(shí)間
@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 存儲(chǔ) 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 存儲(chǔ)
- 引入 DataSource,因?yàn)?JdbcTokenStore 的構(gòu)造方法必須傳入 DataSource
- 創(chuàng)建按 TokenStore,用 AuthJdbcTokenStore 實(shí)現(xiàn)
- 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
- 獲取登錄時(shí)存儲(chǔ)在 Redis 的 token 列表
- token 字符串轉(zhuǎn)換成 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);
}
}
}
解決登出時(shí)長(zhǎng)過長(zhǎng)
場(chǎng)景:項(xiàng)目運(yùn)行一段時(shí)間后,發(fā)現(xiàn)登出時(shí)間越來越慢
問題:通過 debug 發(fā)現(xiàn)耗時(shí)主要在刪除 token 那一段
tokenStore.removeAccessToken(token);
原因:隨著時(shí)間推移,token 越來越多,token 存儲(chǔ)表 oauth_access_token 變得異常的大,所以刪除效率非常差
解決辦法:使用其他 TokenStore,或者清除 oauth_access_token 的表數(shù)據(jù)
到此這篇關(guān)于SpringBoot Security實(shí)現(xiàn)單點(diǎn)登出并清除所有token的文章就介紹到這了,更多相關(guān)SpringBoot Security單點(diǎn)登出內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用EasyExcel實(shí)現(xiàn)百萬級(jí)別數(shù)據(jù)導(dǎo)出的代碼示例
近期需要開發(fā)一個(gè)將百萬數(shù)據(jù)量MySQL8的數(shù)據(jù)導(dǎo)出到excel的功能,所以本文講給大家介紹了基于EasyExcel實(shí)現(xiàn)百萬級(jí)別數(shù)據(jù)導(dǎo)出,文中通過代碼示例講解的非常詳細(xì),需要的朋友可以參考下2023-12-12
idea?Maven?插件?docker-maven-plugin?打包docker鏡像上傳到遠(yuǎn)程倉庫的過程詳解
這篇文章主要介紹了idea Maven插件docker-maven-plugin打包docker鏡像上傳到遠(yuǎn)程倉庫,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-05-05
SpringBoot環(huán)境屬性占位符解析和類型轉(zhuǎn)換方式
這篇文章主要介紹了SpringBoot環(huán)境屬性占位符解析和類型轉(zhuǎn)換方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11
Java中replace與replaceAll的區(qū)別與測(cè)試
replace和replaceAll是JAVA中常用的替換字符的方法,下面這篇文章主要給大家介紹了關(guān)于Java中replace與replaceAll的區(qū)別與測(cè)試,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-09-09
java實(shí)現(xiàn)簡(jiǎn)單學(xué)生成績(jī)檔案管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)簡(jiǎn)單學(xué)生成績(jī)檔案管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05
Java調(diào)用python代碼的五種方式總結(jié)
這篇文章主要給大家介紹了關(guān)于Java調(diào)用python代碼的五種方式,在Java中調(diào)用Python函數(shù)的方法有很多種,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-09-09
spring-redis-session 自定義 key 和過期時(shí)間
這篇文章主要介紹了spring-redis-session 自定義 key 和過期時(shí)間,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12

