From 0a5fdf63498278e7236c17d85b21eca4972c713f Mon Sep 17 00:00:00 2001 From: lpphan <@163.com> Date: Thu, 17 Mar 2022 23:10:35 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=87=E6=9C=9Fkey=E7=9B=91=E5=90=AC?= =?UTF-8?q?=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/store/CustomRedisTokenStore.java | 16 ++- .../common/constant/SecurityConstants.java | 4 + .../redis/template/RedisRepository.java | 22 ++++ .../oauth/config/RedisListenerConfig.java | 24 ++++ .../listener/RedisKeyExpirationListener.java | 111 ++++++++++++++++++ 5 files changed, 174 insertions(+), 3 deletions(-) create mode 100644 zlt-uaa/src/main/java/com/central/oauth/config/RedisListenerConfig.java create mode 100644 zlt-uaa/src/main/java/com/central/oauth/listener/RedisKeyExpirationListener.java diff --git a/zlt-commons/zlt-auth-client-spring-boot-starter/src/main/java/com/central/oauth2/common/store/CustomRedisTokenStore.java b/zlt-commons/zlt-auth-client-spring-boot-starter/src/main/java/com/central/oauth2/common/store/CustomRedisTokenStore.java index ad35a6f..f07ddb0 100644 --- a/zlt-commons/zlt-auth-client-spring-boot-starter/src/main/java/com/central/oauth2/common/store/CustomRedisTokenStore.java +++ b/zlt-commons/zlt-auth-client-spring-boot-starter/src/main/java/com/central/oauth2/common/store/CustomRedisTokenStore.java @@ -39,6 +39,7 @@ import java.util.List; */ public class CustomRedisTokenStore implements TokenStore { private static final String ACCESS = "access:"; + private static final String ACCESS_BAK = "access_bak:"; private static final String AUTH_TO_ACCESS = "auth_to_access:"; private static final String REFRESH_AUTH = "refresh_auth:"; private static final String ACCESS_TO_REFRESH = "access_to_refresh:"; @@ -118,7 +119,7 @@ public class CustomRedisTokenStore implements TokenStore { } private ClientDetails deserializeClientDetails(byte[] bytes) { - return (ClientDetails)redisValueSerializer.deserialize(bytes); + return (ClientDetails) redisValueSerializer.deserialize(bytes); } private byte[] serialize(String string) { @@ -166,7 +167,7 @@ public class CustomRedisTokenStore implements TokenStore { //获取过期时长 int validitySeconds = getAccessTokenValiditySeconds(clientAuth.getClientId()); if (validitySeconds > 0) { - double expiresRatio = token.getExpiresIn() / (double)validitySeconds; + double expiresRatio = token.getExpiresIn() / (double) validitySeconds; //判断是否需要续签,当前剩余时间小于过期时长的50%则续签 if (expiresRatio <= securityProperties.getAuth().getRenew().getTimeRatio()) { //更新AccessToken过期时间 @@ -182,6 +183,7 @@ public class CustomRedisTokenStore implements TokenStore { /** * 判断应用自动续签是否满足白名单和黑名单的过滤逻辑 + * * @param clientId 应用id * @return 是否满足 */ @@ -193,7 +195,7 @@ public class CustomRedisTokenStore implements TokenStore { List exclusiveClientIds = securityProperties.getAuth().getRenew().getExclusiveClientIds(); if (includeClientIds.size() > 0) { result = includeClientIds.contains(clientId); - } else if(exclusiveClientIds.size() > 0) { + } else if (exclusiveClientIds.size() > 0) { result = !exclusiveClientIds.contains(clientId); } return result; @@ -201,6 +203,7 @@ public class CustomRedisTokenStore implements TokenStore { /** * 获取token的总有效时长 + * * @param clientId 应用id */ private int getAccessTokenValiditySeconds(String clientId) { @@ -256,12 +259,14 @@ public class CustomRedisTokenStore implements TokenStore { /** * 存储token + * * @param isRenew 是否续签 */ private void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication, boolean isRenew) { byte[] serializedAccessToken = serialize(token); byte[] serializedAuth = serialize(authentication); byte[] accessKey = serializeKey(ACCESS + token.getValue()); + byte[] accessBakKey = serializeKey(ACCESS_BAK + token.getValue()); byte[] authKey = serializeKey(SecurityConstants.REDIS_TOKEN_AUTH + token.getValue()); byte[] authToAccessKey = serializeKey(AUTH_TO_ACCESS + authenticationKeyGenerator.extractKey(authentication)); byte[] approvalKey = serializeKey(SecurityConstants.REDIS_UNAME_TO_ACCESS + getApprovalKey(authentication)); @@ -279,6 +284,7 @@ public class CustomRedisTokenStore implements TokenStore { if (springDataRedis_2_0) { try { this.redisConnectionSet_2_0.invoke(conn, accessKey, serializedAccessToken); + this.redisConnectionSet_2_0.invoke(conn, accessBakKey, serializedAccessToken); this.redisConnectionSet_2_0.invoke(conn, authKey, serializedAuth); this.redisConnectionSet_2_0.invoke(conn, authToAccessKey, serializedAccessToken); } catch (Exception ex) { @@ -286,6 +292,7 @@ public class CustomRedisTokenStore implements TokenStore { } } else { conn.set(accessKey, serializedAccessToken); + conn.set(accessBakKey, serializedAccessToken); conn.set(authKey, serializedAuth); conn.set(authToAccessKey, serializedAccessToken); } @@ -303,6 +310,7 @@ public class CustomRedisTokenStore implements TokenStore { if (token.getExpiration() != null) { int seconds = token.getExpiresIn(); conn.expire(accessKey, seconds); + conn.expire(accessBakKey, seconds + 60); conn.expire(authKey, seconds); conn.expire(authToAccessKey, seconds); conn.expire(clientId, seconds); @@ -363,6 +371,7 @@ public class CustomRedisTokenStore implements TokenStore { public void removeAccessToken(String tokenValue) { byte[] accessKey = serializeKey(ACCESS + tokenValue); + byte[] accessBakKey = serializeKey(ACCESS_BAK + tokenValue); byte[] authKey = serializeKey(SecurityConstants.REDIS_TOKEN_AUTH + tokenValue); byte[] accessToRefreshKey = serializeKey(ACCESS_TO_REFRESH + tokenValue); RedisConnection conn = getConnection(); @@ -371,6 +380,7 @@ public class CustomRedisTokenStore implements TokenStore { byte[] auth = conn.get(authKey); conn.openPipeline(); conn.del(accessKey); + conn.del(accessBakKey); conn.del(accessToRefreshKey); // Don't remove the refresh token - it's up to the caller to do that conn.del(authKey); diff --git a/zlt-commons/zlt-common-core/src/main/java/com/central/common/constant/SecurityConstants.java b/zlt-commons/zlt-common-core/src/main/java/com/central/common/constant/SecurityConstants.java index 45f0c12..4cca964 100644 --- a/zlt-commons/zlt-common-core/src/main/java/com/central/common/constant/SecurityConstants.java +++ b/zlt-commons/zlt-common-core/src/main/java/com/central/common/constant/SecurityConstants.java @@ -154,6 +154,10 @@ public interface SecurityConstants { * redis中授权token对应的key */ String REDIS_TOKEN_AUTH = "auth:"; + /** + * 值同access 过期时间+60 + */ + String ACCESS_BAK = "access_bak:"; /** * redis中应用对应的token集合的key */ diff --git a/zlt-commons/zlt-redis-spring-boot-starter/src/main/java/com/central/common/redis/template/RedisRepository.java b/zlt-commons/zlt-redis-spring-boot-starter/src/main/java/com/central/common/redis/template/RedisRepository.java index d6c1162..d73cfca 100644 --- a/zlt-commons/zlt-redis-spring-boot-starter/src/main/java/com/central/common/redis/template/RedisRepository.java +++ b/zlt-commons/zlt-redis-spring-boot-starter/src/main/java/com/central/common/redis/template/RedisRepository.java @@ -1,6 +1,7 @@ package com.central.common.redis.template; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.dao.DataAccessException; import org.springframework.data.redis.connection.RedisClusterNode; import org.springframework.data.redis.connection.RedisConnection; @@ -178,6 +179,26 @@ public class RedisRepository { public Object get(final String key) { return redisTemplate.opsForValue().get(key); } + + /** + *获取原来key键对应的值并重新赋新值。 + * @param key + * @param value + * @return + */ + public String getAndSet(final String key,Object value) { + String result = null; + if (StringUtils.isEmpty(key)){ + log.error("key不能为空"); + return null; + } + try { + result = (String) redisTemplate.opsForValue().getAndSet(key, value); + }catch (Exception e){ + e.printStackTrace(); + } + return result; + } /** * 根据key获取对象 * @@ -190,6 +211,7 @@ public class RedisRepository { return redisTemplate.execute(connection -> deserializeValue(connection.get(rawKey), valueSerializer), true); } + /** * Ops for hash hash operations. * diff --git a/zlt-uaa/src/main/java/com/central/oauth/config/RedisListenerConfig.java b/zlt-uaa/src/main/java/com/central/oauth/config/RedisListenerConfig.java new file mode 100644 index 0000000..3cfe01c --- /dev/null +++ b/zlt-uaa/src/main/java/com/central/oauth/config/RedisListenerConfig.java @@ -0,0 +1,24 @@ +package com.central.oauth.config; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.listener.RedisMessageListenerContainer; +/** + * + * redis过期key监听器配置类 + * @author zlt + * + */ +@Configuration +public class RedisListenerConfig { + @Bean + @Primary + public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory connectionFactory) { + RedisMessageListenerContainer container = new RedisMessageListenerContainer(); + container.setConnectionFactory(connectionFactory); + return container; + } +} diff --git a/zlt-uaa/src/main/java/com/central/oauth/listener/RedisKeyExpirationListener.java b/zlt-uaa/src/main/java/com/central/oauth/listener/RedisKeyExpirationListener.java new file mode 100644 index 0000000..b4a4913 --- /dev/null +++ b/zlt-uaa/src/main/java/com/central/oauth/listener/RedisKeyExpirationListener.java @@ -0,0 +1,111 @@ +package com.central.oauth.listener; + +import com.central.common.constant.SecurityConstants; +import com.central.common.redis.template.RedisRepository; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.data.redis.connection.Message; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.listener.KeyExpirationEventMessageListener; +import org.springframework.data.redis.listener.RedisMessageListenerContainer; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.token.store.redis.JdkSerializationStrategy; +import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStoreSerializationStrategy; +import org.springframework.stereotype.Component; + +/** + * + * redis过期key监听器 + * @author zlt + * + */ +@Component +@Slf4j +public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener { + @Autowired + private RedisRepository redisRepository; + private RedisTokenStoreSerializationStrategy serializationStrategy = new JdkSerializationStrategy(); + private final RedisConnectionFactory connectionFactory; + + public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer, RedisConnectionFactory connectionFactory) { + super(listenerContainer); + this.connectionFactory = connectionFactory; + } + + @Override + public void onMessage(Message message, byte[] pattern) { + if (message == null) { + log.debug("message不能为空"); + return; + } + //获取失效的的key + String expiredKey = message.toString(); + if (StringUtils.isEmpty(expiredKey)) { + log.debug("expiredKey不能为空"); + return; + } + String accesskey = expiredKey.substring(0, expiredKey.indexOf(":") + 1); + if (!"access:".equals(accesskey)) { + log.debug("非需要监听key,跳过"); + return; + } + String accessValue = expiredKey.substring(expiredKey.indexOf(":") + 1); + // 分布式集群部署下防止一个过期被多个服务重复消费 + String qc = redisRepository.getAndSet("qc:" + accessValue, "1"); + if (StringUtils.isNotEmpty(qc) && "1".equals(qc)) { + log.debug("其他节点已经处理了该数据,次数跳过"); + return; + } + byte[] accessBakKey = serializeKey(SecurityConstants.ACCESS_BAK + accessValue); + byte[] authKey = serializeKey(SecurityConstants.REDIS_TOKEN_AUTH + accessValue); + RedisConnection conn = getConnection(); + try { + byte[] access = conn.get(accessBakKey); + byte[] auth = conn.get(authKey); + OAuth2Authentication authentication = deserializeAuthentication(auth); + if (authentication != null) { + byte[] unameKey = serializeKey(SecurityConstants.REDIS_UNAME_TO_ACCESS + getApprovalKey(authentication)); + byte[] clientId = serializeKey(SecurityConstants.REDIS_CLIENT_ID_TO_ACCESS + authentication.getOAuth2Request().getClientId()); + conn.openPipeline(); + conn.lRem(unameKey, 1, access); + conn.lRem(clientId, 1, access); + conn.closePipeline(); + } + } catch (Exception e) { + log.error(e.getMessage()); + } finally { + conn.del(); + conn.close(); + } + + } + + private byte[] serializeKey(String object) { + return serialize("" + object); + } + + private byte[] serialize(String string) { + return serializationStrategy.serialize(string); + } + + private RedisConnection getConnection() { + return connectionFactory.getConnection(); + } + + private OAuth2Authentication deserializeAuthentication(byte[] bytes) { + return serializationStrategy.deserialize(bytes, OAuth2Authentication.class); + } + + private static String getApprovalKey(OAuth2Authentication authentication) { + String userName = authentication.getUserAuthentication() == null ? "" + : authentication.getUserAuthentication().getName(); + return getApprovalKey(authentication.getOAuth2Request().getClientId(), userName); + } + + private static String getApprovalKey(String clientId, String userName) { + return clientId + (userName == null ? "" : ":" + userName); + } +} -- GitLab