From 560129fc06591e783111c297f045288b120b1dbc Mon Sep 17 00:00:00 2001 From: PIG AI Date: Wed, 21 May 2025 17:00:39 +0800 Subject: [PATCH] =?UTF-8?q?feat(security):=20=E4=BC=98=E5=8C=96oauth2.0=20?= =?UTF-8?q?=E7=9A=84=E8=87=AA=E7=9C=81=E7=AB=AF=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在PigxRemoteRegisteredClientRepository中实现findById方法 - 优化PigxRedisOAuth2AuthorizationService的token处理逻辑 - 修复findByToken方法中tokenType为null时的默认值处理 - 添加根据username清理token的功能扩展 --- .../PigxRedisOAuth2AuthorizationService.java | 253 +++++++++--------- .../PigxRemoteRegisteredClientRepository.java | 2 +- 2 files changed, 128 insertions(+), 127 deletions(-) diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxRedisOAuth2AuthorizationService.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxRedisOAuth2AuthorizationService.java index e67997c0b..b3b74c1cf 100644 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxRedisOAuth2AuthorizationService.java +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxRedisOAuth2AuthorizationService.java @@ -31,159 +31,160 @@ import java.util.concurrent.TimeUnit; @RequiredArgsConstructor public class PigxRedisOAuth2AuthorizationService implements OAuth2AuthorizationService { - private final static Long TIMEOUT = 10L; + private final static Long TIMEOUT = 10L; - private static final String AUTHORIZATION = "token"; + private static final String AUTHORIZATION = "token"; - private final RedisTemplate redisTemplate; + private final RedisTemplate redisTemplate; - private final KeyStrResolver tenantKeyStrResolver; + private final KeyStrResolver tenantKeyStrResolver; - @Override - public void save(OAuth2Authorization authorization) { - Assert.notNull(authorization, "authorization cannot be null"); + private static boolean isState(OAuth2Authorization authorization) { + return Objects.nonNull(authorization.getAttribute("state")); + } - if (isState(authorization)) { - String token = authorization.getAttribute("state"); - redisTemplate.setValueSerializer(RedisSerializer.java()); - redisTemplate.opsForValue() - .set(buildKey(OAuth2ParameterNames.STATE, token), authorization, TIMEOUT, TimeUnit.MINUTES); - } + private static boolean isCode(OAuth2Authorization authorization) { + OAuth2Authorization.Token authorizationCode = authorization + .getToken(OAuth2AuthorizationCode.class); + return Objects.nonNull(authorizationCode); + } - if (isCode(authorization)) { - OAuth2Authorization.Token authorizationCode = authorization - .getToken(OAuth2AuthorizationCode.class); - OAuth2AuthorizationCode authorizationCodeToken = authorizationCode.getToken(); - long between = ChronoUnit.MINUTES.between(authorizationCodeToken.getIssuedAt(), - authorizationCodeToken.getExpiresAt()); - redisTemplate.setValueSerializer(RedisSerializer.java()); - redisTemplate.opsForValue() - .set(buildKey(OAuth2ParameterNames.CODE, authorizationCodeToken.getTokenValue()), authorization, - between, TimeUnit.MINUTES); - } + private static boolean isRefreshToken(OAuth2Authorization authorization) { + return Objects.nonNull(authorization.getRefreshToken()); + } - if (isRefreshToken(authorization)) { - OAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken(); - long between = ChronoUnit.SECONDS.between(refreshToken.getIssuedAt(), refreshToken.getExpiresAt()); - redisTemplate.setValueSerializer(RedisSerializer.java()); - redisTemplate.opsForValue() - .set(buildKey(OAuth2ParameterNames.REFRESH_TOKEN, refreshToken.getTokenValue()), authorization, between, - TimeUnit.SECONDS); - } + private static boolean isAccessToken(OAuth2Authorization authorization) { + return Objects.nonNull(authorization.getAccessToken()); + } - if (isAccessToken(authorization)) { - OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); - long between = ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt()); - redisTemplate.setValueSerializer(RedisSerializer.java()); - redisTemplate.opsForValue() - .set(buildKey(OAuth2ParameterNames.ACCESS_TOKEN, accessToken.getTokenValue()), authorization, between, - TimeUnit.SECONDS); + @Override + public void save(OAuth2Authorization authorization) { + Assert.notNull(authorization, "authorization cannot be null"); - // 扩展记录 access-token 、username 的关系 1::token::username::admin::xxx - String tokenUsername = String.format("%s::%s::%s::%s::%s", tenantKeyStrResolver.key(), AUTHORIZATION, - SecurityConstants.DETAILS_USERNAME, authorization.getPrincipalName(), accessToken.getTokenValue()); - redisTemplate.opsForValue().set(tokenUsername, accessToken.getTokenValue(), between, TimeUnit.SECONDS); - } - } + if (isState(authorization)) { + String token = authorization.getAttribute("state"); + redisTemplate.setValueSerializer(RedisSerializer.java()); + redisTemplate.opsForValue() + .set(buildKey(OAuth2ParameterNames.STATE, token), authorization, TIMEOUT, TimeUnit.MINUTES); + } - @Override - public void remove(OAuth2Authorization authorization) { - Assert.notNull(authorization, "authorization cannot be null"); + if (isCode(authorization)) { + OAuth2Authorization.Token authorizationCode = authorization + .getToken(OAuth2AuthorizationCode.class); + OAuth2AuthorizationCode authorizationCodeToken = authorizationCode.getToken(); + long between = ChronoUnit.MINUTES.between(authorizationCodeToken.getIssuedAt(), + authorizationCodeToken.getExpiresAt()); + redisTemplate.setValueSerializer(RedisSerializer.java()); + redisTemplate.opsForValue() + .set(buildKey(OAuth2ParameterNames.CODE, authorizationCodeToken.getTokenValue()), authorization, + between, TimeUnit.MINUTES); + } - List keys = new ArrayList<>(); - if (isState(authorization)) { - String token = authorization.getAttribute("state"); - keys.add(buildKey(OAuth2ParameterNames.STATE, token)); - } + if (isRefreshToken(authorization)) { + OAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken(); + long between = ChronoUnit.SECONDS.between(refreshToken.getIssuedAt(), refreshToken.getExpiresAt()); + redisTemplate.setValueSerializer(RedisSerializer.java()); + redisTemplate.opsForValue() + .set(buildKey(OAuth2ParameterNames.REFRESH_TOKEN, refreshToken.getTokenValue()), authorization, between, + TimeUnit.SECONDS); + } - if (isCode(authorization)) { - OAuth2Authorization.Token authorizationCode = authorization - .getToken(OAuth2AuthorizationCode.class); - OAuth2AuthorizationCode authorizationCodeToken = authorizationCode.getToken(); - keys.add(buildKey(OAuth2ParameterNames.CODE, authorizationCodeToken.getTokenValue())); - } + if (isAccessToken(authorization)) { + OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); + long between = ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt()); + redisTemplate.setValueSerializer(RedisSerializer.java()); + redisTemplate.opsForValue() + .set(buildKey(OAuth2ParameterNames.ACCESS_TOKEN, accessToken.getTokenValue()), authorization, between, + TimeUnit.SECONDS); - if (isRefreshToken(authorization)) { - OAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken(); - keys.add(buildKey(OAuth2ParameterNames.REFRESH_TOKEN, refreshToken.getTokenValue())); - } + // 扩展记录 access-token 、username 的关系 1::token::username::admin::xxx + String tokenUsername = String.format("%s::%s::%s::%s::%s", tenantKeyStrResolver.key(), AUTHORIZATION, + SecurityConstants.DETAILS_USERNAME, authorization.getPrincipalName(), accessToken.getTokenValue()); + redisTemplate.opsForValue().set(tokenUsername, accessToken.getTokenValue(), between, TimeUnit.SECONDS); + } + } - if (isAccessToken(authorization)) { - OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); - keys.add(buildKey(OAuth2ParameterNames.ACCESS_TOKEN, accessToken.getTokenValue())); + @Override + public void remove(OAuth2Authorization authorization) { + Assert.notNull(authorization, "authorization cannot be null"); - // 扩展记录 access-token 、username 的关系 1::token::username::admin::xxx - String key = String.format("%s::%s::%s::%s::%s", tenantKeyStrResolver.key(), AUTHORIZATION, - SecurityConstants.DETAILS_USERNAME, authorization.getPrincipalName(), accessToken.getTokenValue()); - keys.add(key); - } + List keys = new ArrayList<>(); + if (isState(authorization)) { + String token = authorization.getAttribute("state"); + keys.add(buildKey(OAuth2ParameterNames.STATE, token)); + } - redisTemplate.delete(keys); - } + if (isCode(authorization)) { + OAuth2Authorization.Token authorizationCode = authorization + .getToken(OAuth2AuthorizationCode.class); + OAuth2AuthorizationCode authorizationCodeToken = authorizationCode.getToken(); + keys.add(buildKey(OAuth2ParameterNames.CODE, authorizationCodeToken.getTokenValue())); + } - @Override - @Nullable - public OAuth2Authorization findById(String id) { - throw new UnsupportedOperationException(); - } + if (isRefreshToken(authorization)) { + OAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken(); + keys.add(buildKey(OAuth2ParameterNames.REFRESH_TOKEN, refreshToken.getTokenValue())); + } - @Override - @Nullable - public OAuth2Authorization findByToken(String token, @Nullable OAuth2TokenType tokenType) { - Assert.hasText(token, "token cannot be empty"); - Assert.notNull(tokenType, "tokenType cannot be empty"); - redisTemplate.setValueSerializer(RedisSerializer.java()); - return (OAuth2Authorization) redisTemplate.opsForValue().get(buildKey(tokenType.getValue(), token)); - } + if (isAccessToken(authorization)) { + OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); + keys.add(buildKey(OAuth2ParameterNames.ACCESS_TOKEN, accessToken.getTokenValue())); - private String buildKey(String type, String id) { - return String.format("%s::%s::%s::%s", tenantKeyStrResolver.key(), AUTHORIZATION, type, id); - } + // 扩展记录 access-token 、username 的关系 1::token::username::admin::xxx + String key = String.format("%s::%s::%s::%s::%s", tenantKeyStrResolver.key(), AUTHORIZATION, + SecurityConstants.DETAILS_USERNAME, authorization.getPrincipalName(), accessToken.getTokenValue()); + keys.add(key); + } - private static boolean isState(OAuth2Authorization authorization) { - return Objects.nonNull(authorization.getAttribute("state")); - } + redisTemplate.delete(keys); + } - private static boolean isCode(OAuth2Authorization authorization) { - OAuth2Authorization.Token authorizationCode = authorization - .getToken(OAuth2AuthorizationCode.class); - return Objects.nonNull(authorizationCode); - } + @Override + @Nullable + public OAuth2Authorization findById(String id) { + throw new UnsupportedOperationException(); + } - private static boolean isRefreshToken(OAuth2Authorization authorization) { - return Objects.nonNull(authorization.getRefreshToken()); - } + @Override + @Nullable + public OAuth2Authorization findByToken(String token, @Nullable OAuth2TokenType tokenType) { + Assert.hasText(token, "token cannot be empty"); + redisTemplate.setValueSerializer(RedisSerializer.java()); + return (OAuth2Authorization) redisTemplate.opsForValue() + .get(buildKey(Objects.requireNonNullElse(tokenType, OAuth2TokenType.ACCESS_TOKEN).getValue(), token)); + } - private static boolean isAccessToken(OAuth2Authorization authorization) { - return Objects.nonNull(authorization.getAccessToken()); - } + private String buildKey(String type, String id) { + return String.format("%s::%s::%s::%s", tenantKeyStrResolver.key(), AUTHORIZATION, type, id); + } - /** - * 扩展方法根据 username 查询是否存在存储的 - * @param authentication - * @return - */ - public void removeByUsername(Authentication authentication) { - // 根据 username查询对应access-token - String authenticationName = authentication.getName(); + /** + * 扩展方法根据 username 查询是否存在存储的 + * + * @param authentication + * @return + */ + public void removeByUsername(Authentication authentication) { + // 根据 username查询对应access-token + String authenticationName = authentication.getName(); - // 扩展记录 access-token 、username 的关系 1::token::username::admin::xxx - String tokenUsernameKey = String.format("%s::%s::%s::%s::*", tenantKeyStrResolver.key(), AUTHORIZATION, - SecurityConstants.DETAILS_USERNAME, authenticationName); - Set keys = redisTemplate.keys(tokenUsernameKey); - if (CollUtil.isEmpty(keys)) { - return; - } + // 扩展记录 access-token 、username 的关系 1::token::username::admin::xxx + String tokenUsernameKey = String.format("%s::%s::%s::%s::*", tenantKeyStrResolver.key(), AUTHORIZATION, + SecurityConstants.DETAILS_USERNAME, authenticationName); + Set keys = redisTemplate.keys(tokenUsernameKey); + if (CollUtil.isEmpty(keys)) { + return; + } - List tokenList = redisTemplate.opsForValue().multiGet(keys); + List tokenList = redisTemplate.opsForValue().multiGet(keys); - for (Object token : tokenList) { - // 根据token 查询存储的 OAuth2Authorization - OAuth2Authorization authorization = this.findByToken((String) token, OAuth2TokenType.ACCESS_TOKEN); - // 根据 OAuth2Authorization 删除相关令牌 - this.remove(authorization); - } + for (Object token : tokenList) { + // 根据token 查询存储的 OAuth2Authorization + OAuth2Authorization authorization = this.findByToken((String) token, OAuth2TokenType.ACCESS_TOKEN); + // 根据 OAuth2Authorization 删除相关令牌 + this.remove(authorization); + } - } + } } diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxRemoteRegisteredClientRepository.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxRemoteRegisteredClientRepository.java index 1289e49b4..83e2383ed 100644 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxRemoteRegisteredClientRepository.java +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxRemoteRegisteredClientRepository.java @@ -76,7 +76,7 @@ public class PigxRemoteRegisteredClientRepository implements RegisteredClientRep */ @Override public RegisteredClient findById(String id) { - throw new UnsupportedOperationException(); + return this.findByClientId(id); } /**