mirror of
http://git.pig4cloud.com/pig/pigx.git
synced 2026-11-08 20:49:00 +08:00
feat(security): 重构Redis存储的OAuth2授权服务
- 将PigxRedisOAuth2AuthorizationService迁移至common-security模块 - 使用RedisTemplate替代RedisUtils进行缓存操作 - 增加Jackson序列化支持RouteDefinitionVo - 优化token存储逻辑与过期时间处理 - 统一授权服务与路由管理的Redis配置
This commit is contained in:
1 parent
d392f2f03a
commit
c8edf16536
5 files changed
+57
-38
No files matched your search
+1
-1
@@ -2,8 +2,8 @@ package com.pig4cloud.pigx.auth.support.base;
|
||||
|
||||
import cn.hutool.core.util.BooleanUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import com.pig4cloud.pigx.auth.support.store.PigxRedisOAuth2AuthorizationService;
|
||||
import com.pig4cloud.pigx.common.core.constant.CommonConstants;
|
||||
import com.pig4cloud.pigx.common.security.service.PigxRedisOAuth2AuthorizationService;
|
||||
import com.pig4cloud.pigx.common.security.util.OAuth2ErrorCodesExpand;
|
||||
import com.pig4cloud.pigx.common.security.util.ScopeException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
+11
-7
@@ -1,37 +1,41 @@
|
||||
package com.pig4cloud.pigx.auth.support.store;
|
||||
package com.pig4cloud.pigx.common.security.service;
|
||||
|
||||
import com.pig4cloud.pigx.common.data.cache.RedisUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@Service
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class PigxRedisOAuth2AuthorizationConsentService implements OAuth2AuthorizationConsentService {
|
||||
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
private final static Long TIMEOUT = 10L;
|
||||
|
||||
@Override
|
||||
public void save(OAuth2AuthorizationConsent authorizationConsent) {
|
||||
Assert.notNull(authorizationConsent, "authorizationConsent cannot be null");
|
||||
|
||||
RedisUtils.set(buildKey(authorizationConsent), authorizationConsent, TIMEOUT * 60);
|
||||
redisTemplate.opsForValue()
|
||||
.set(buildKey(authorizationConsent), authorizationConsent, TIMEOUT, TimeUnit.MINUTES);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(OAuth2AuthorizationConsent authorizationConsent) {
|
||||
Assert.notNull(authorizationConsent, "authorizationConsent cannot be null");
|
||||
RedisUtils.delete(buildKey(authorizationConsent));
|
||||
redisTemplate.delete(buildKey(authorizationConsent));
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2AuthorizationConsent findById(String registeredClientId, String principalName) {
|
||||
Assert.hasText(registeredClientId, "registeredClientId cannot be empty");
|
||||
Assert.hasText(principalName, "principalName cannot be empty");
|
||||
return RedisUtils.get(buildKey(registeredClientId, principalName));
|
||||
return (OAuth2AuthorizationConsent) redisTemplate.opsForValue()
|
||||
.get(buildKey(registeredClientId, principalName));
|
||||
}
|
||||
|
||||
private static String buildKey(String registeredClientId, String principalName) {
|
||||
+31
-20
@@ -1,10 +1,10 @@
|
||||
package com.pig4cloud.pigx.auth.support.store;
|
||||
package com.pig4cloud.pigx.common.security.service;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
|
||||
import com.pig4cloud.pigx.common.data.cache.RedisUtils;
|
||||
import com.pig4cloud.pigx.common.security.service.PigxUser;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
@@ -15,7 +15,6 @@ import org.springframework.security.oauth2.server.authorization.OAuth2Authorizat
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.security.Principal;
|
||||
@@ -24,14 +23,12 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 基于Redis实现的OAuth2授权服务类,提供授权信息的存储、查询和删除功能
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2025/06/30
|
||||
* @date 2022/5/27
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class PigxRedisOAuth2AuthorizationService implements OAuth2AuthorizationService {
|
||||
|
||||
@@ -39,6 +36,8 @@ public class PigxRedisOAuth2AuthorizationService implements OAuth2AuthorizationS
|
||||
|
||||
private static final String AUTHORIZATION = "token";
|
||||
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
private static boolean isState(OAuth2Authorization authorization) {
|
||||
return Objects.nonNull(authorization.getAttribute("state"));
|
||||
}
|
||||
@@ -63,7 +62,9 @@ public class PigxRedisOAuth2AuthorizationService implements OAuth2AuthorizationS
|
||||
|
||||
if (isState(authorization)) {
|
||||
String token = authorization.getAttribute("state");
|
||||
RedisUtils.set(buildKey(OAuth2ParameterNames.STATE, token), authorization, TIMEOUT * 60);
|
||||
redisTemplate.setValueSerializer(RedisSerializer.java());
|
||||
redisTemplate.opsForValue()
|
||||
.set(buildKey(OAuth2ParameterNames.STATE, token), authorization, TIMEOUT, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
if (isCode(authorization)) {
|
||||
@@ -72,20 +73,28 @@ public class PigxRedisOAuth2AuthorizationService implements OAuth2AuthorizationS
|
||||
OAuth2AuthorizationCode authorizationCodeToken = authorizationCode.getToken();
|
||||
long between = ChronoUnit.MINUTES.between(authorizationCodeToken.getIssuedAt(),
|
||||
authorizationCodeToken.getExpiresAt());
|
||||
RedisUtils.set(buildKey(OAuth2ParameterNames.CODE, authorizationCodeToken.getTokenValue()), authorization,
|
||||
(int) (between * 60));
|
||||
redisTemplate.setValueSerializer(RedisSerializer.java());
|
||||
redisTemplate.opsForValue()
|
||||
.set(buildKey(OAuth2ParameterNames.CODE, authorizationCodeToken.getTokenValue()), authorization,
|
||||
between, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
if (isRefreshToken(authorization)) {
|
||||
OAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken();
|
||||
long between = ChronoUnit.SECONDS.between(refreshToken.getIssuedAt(), refreshToken.getExpiresAt());
|
||||
RedisUtils.set(buildKey(OAuth2ParameterNames.REFRESH_TOKEN, refreshToken.getTokenValue()), authorization, (int) between);
|
||||
redisTemplate.setValueSerializer(RedisSerializer.java());
|
||||
redisTemplate.opsForValue()
|
||||
.set(buildKey(OAuth2ParameterNames.REFRESH_TOKEN, refreshToken.getTokenValue()), authorization, between,
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
if (isAccessToken(authorization)) {
|
||||
OAuth2AccessToken accessToken = authorization.getAccessToken().getToken();
|
||||
long between = ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt());
|
||||
RedisUtils.set(buildKey(OAuth2ParameterNames.ACCESS_TOKEN, accessToken.getTokenValue()), authorization, (int) between);
|
||||
redisTemplate.setValueSerializer(RedisSerializer.java());
|
||||
redisTemplate.opsForValue()
|
||||
.set(buildKey(OAuth2ParameterNames.ACCESS_TOKEN, accessToken.getTokenValue()), authorization, between,
|
||||
TimeUnit.SECONDS);
|
||||
|
||||
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = authorization.getAttribute(Principal.class.getName());
|
||||
|
||||
@@ -93,7 +102,7 @@ public class PigxRedisOAuth2AuthorizationService implements OAuth2AuthorizationS
|
||||
// 扩展记录 access-token 、username 的关系 token::username::admin::tenantId::xxx
|
||||
String tokenUsername = String.format("%s::%s::%s::%s::%s::%s", AUTHORIZATION,
|
||||
SecurityConstants.DETAILS_USERNAME, authorization.getPrincipalName(), authorization.getRegisteredClientId(), pigxUser.getTenantId(), accessToken.getTokenValue());
|
||||
RedisUtils.set(tokenUsername, accessToken.getTokenValue(), (int) between);
|
||||
redisTemplate.opsForValue().set(tokenUsername, accessToken.getTokenValue(), between, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,7 +139,7 @@ public class PigxRedisOAuth2AuthorizationService implements OAuth2AuthorizationS
|
||||
keys.add(key);
|
||||
}
|
||||
|
||||
RedisUtils.delete(keys.toArray(new String[0]));
|
||||
redisTemplate.delete(keys);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -143,7 +152,9 @@ public class PigxRedisOAuth2AuthorizationService implements OAuth2AuthorizationS
|
||||
@Nullable
|
||||
public OAuth2Authorization findByToken(String token, @Nullable OAuth2TokenType tokenType) {
|
||||
Assert.hasText(token, "token cannot be empty");
|
||||
return RedisUtils.get(buildKey(Objects.requireNonNullElse(tokenType, OAuth2TokenType.ACCESS_TOKEN).getValue(), token));
|
||||
redisTemplate.setValueSerializer(RedisSerializer.java());
|
||||
return (OAuth2Authorization) redisTemplate.opsForValue()
|
||||
.get(buildKey(Objects.requireNonNullElse(tokenType, OAuth2TokenType.ACCESS_TOKEN).getValue(), token));
|
||||
}
|
||||
|
||||
private String buildKey(String type, String id) {
|
||||
@@ -162,16 +173,16 @@ public class PigxRedisOAuth2AuthorizationService implements OAuth2AuthorizationS
|
||||
// 扩展记录 access-token 、username 的关系 1::token::username::admin::xxx
|
||||
String tokenUsernameKey = String.format("%s::%s::%s::*", AUTHORIZATION,
|
||||
SecurityConstants.DETAILS_USERNAME, authenticationName);
|
||||
Set<String> keys = RedisUtils.keys(tokenUsernameKey);
|
||||
Set<String> keys = redisTemplate.keys(tokenUsernameKey);
|
||||
if (CollUtil.isEmpty(keys)) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> tokenList = RedisUtils.multiGet(keys.stream().toList());
|
||||
List<Object> tokenList = redisTemplate.opsForValue().multiGet(keys);
|
||||
|
||||
for (String token : tokenList) {
|
||||
for (Object token : tokenList) {
|
||||
// 根据token 查询存储的 OAuth2Authorization
|
||||
OAuth2Authorization authorization = this.findByToken(token, OAuth2TokenType.ACCESS_TOKEN);
|
||||
OAuth2Authorization authorization = this.findByToken((String) token, OAuth2TokenType.ACCESS_TOKEN);
|
||||
// 根据 OAuth2Authorization 删除相关令牌
|
||||
this.remove(authorization);
|
||||
}
|
||||
+2
@@ -2,6 +2,8 @@ com.pig4cloud.pigx.common.security.service.PigxDefaultUserDetailsServiceImpl
|
||||
com.pig4cloud.pigx.common.security.service.PigxTocDefaultUserDetailsServiceImpl
|
||||
com.pig4cloud.pigx.common.security.service.PigxMobileUserDetailServiceImpl
|
||||
com.pig4cloud.pigx.common.security.service.PigxTocMobileUserDetailsServiceImpl
|
||||
com.pig4cloud.pigx.common.security.service.PigxRedisOAuth2AuthorizationService
|
||||
com.pig4cloud.pigx.common.security.service.PigxRedisOAuth2AuthorizationConsentService
|
||||
com.pig4cloud.pigx.common.security.component.PigxSecurityInnerAspect
|
||||
com.pig4cloud.pigx.common.security.component.PigxSecurityMessageSourceConfiguration
|
||||
com.pig4cloud.pigx.common.security.service.PigxRemoteRegisteredClientRepository
|
||||
+12
-10
@@ -21,7 +21,6 @@ import cn.hutool.json.JSONArray;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.pig4cloud.pigx.admin.service.SysRouteConfService;
|
||||
import com.pig4cloud.pigx.common.core.constant.CacheConstants;
|
||||
import com.pig4cloud.pigx.common.data.cache.RedisUtils;
|
||||
import com.pig4cloud.pigx.common.gateway.support.DynamicRouteInitEvent;
|
||||
import com.pig4cloud.pigx.common.gateway.vo.RouteDefinitionVo;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -34,9 +33,11 @@ import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.data.redis.core.RedisCallback;
|
||||
import org.springframework.data.redis.core.HashOperations;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.listener.ChannelTopic;
|
||||
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
|
||||
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.transaction.event.TransactionalEventListener;
|
||||
|
||||
@@ -44,10 +45,10 @@ import java.net.URI;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 动态路由初始化运行器:容器启动后保存配置文件里面的路由信息到Redis
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2018/10/31
|
||||
* <p>
|
||||
* 容器启动后保存配置文件里面的路由信息到Redis
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@@ -55,6 +56,8 @@ import java.util.Map;
|
||||
@ConditionalOnDiscoveryEnabled
|
||||
public class DynamicRouteInitRunner implements InitializingBean {
|
||||
|
||||
private final RedisTemplate<String, String> redisTemplate;
|
||||
|
||||
private final SysRouteConfService routeConfService;
|
||||
|
||||
private final RedisMessageListenerContainer listenerContainer;
|
||||
@@ -73,7 +76,7 @@ public class DynamicRouteInitRunner implements InitializingBean {
|
||||
@Order
|
||||
@TransactionalEventListener({DynamicRouteInitEvent.class})
|
||||
public void initRoute() {
|
||||
RedisUtils.delete(CacheConstants.ROUTE_KEY);
|
||||
redisTemplate.delete(CacheConstants.ROUTE_KEY);
|
||||
log.info("开始初始化网关路由");
|
||||
|
||||
routeConfService.list().forEach(route -> {
|
||||
@@ -90,14 +93,13 @@ public class DynamicRouteInitRunner implements InitializingBean {
|
||||
vo.setMetadata(JSONUtil.toBean(route.getMetadata(), Map.class));
|
||||
log.info("加载路由ID:{},{}", route.getRouteId(), vo);
|
||||
|
||||
RedisUtils.hset(CacheConstants.ROUTE_KEY, route.getRouteId(), vo);
|
||||
redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(RouteDefinitionVo.class));
|
||||
HashOperations<String, String, RouteDefinitionVo> stringStringValueOperations = redisTemplate.opsForHash();
|
||||
stringStringValueOperations.put(CacheConstants.ROUTE_KEY, route.getRouteId(), vo);
|
||||
});
|
||||
|
||||
// 通知网关重置路由
|
||||
RedisUtils.execute((RedisCallback<Void>) connection -> {
|
||||
connection.publish(CacheConstants.ROUTE_JVM_RELOAD_TOPIC.getBytes(), "路由信息,网关缓存更新".getBytes());
|
||||
return null;
|
||||
});
|
||||
redisTemplate.convertAndSend(CacheConstants.ROUTE_JVM_RELOAD_TOPIC, "路由信息,网关缓存更新");
|
||||
log.debug("初始化网关路由结束 ");
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user