diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/config/AuthorizationServerConfig.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/config/AuthorizationServerConfig.java index efd29d31e..60a35b468 100644 --- a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/config/AuthorizationServerConfig.java +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/config/AuthorizationServerConfig.java @@ -19,11 +19,7 @@ package com.pig4cloud.pigx.auth.config; -import cn.hutool.core.util.StrUtil; import com.fasterxml.jackson.databind.ObjectMapper; -import com.pig4cloud.pigx.auth.service.PigxRedisTokenStore; -import com.pig4cloud.pigx.common.core.constant.SecurityConstants; -import com.pig4cloud.pigx.common.data.tenant.TenantContextHolder; import com.pig4cloud.pigx.common.security.component.PigxCommenceAuthExceptionEntryPoint; import com.pig4cloud.pigx.common.security.component.PigxWebResponseExceptionTranslator; import lombok.AllArgsConstructor; @@ -38,10 +34,9 @@ import org.springframework.security.oauth2.config.annotation.web.configuration.E import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; import org.springframework.security.oauth2.provider.ClientDetailsService; -import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices; -import org.springframework.security.oauth2.provider.token.DefaultAuthenticationKeyGenerator; import org.springframework.security.oauth2.provider.token.TokenEnhancer; +import org.springframework.security.oauth2.provider.token.TokenStore; /** * @author lengleng @@ -60,9 +55,9 @@ public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdap private final AuthorizationCodeServices authorizationCodeServices; - private final PigxRedisTokenStore redisTokenStore; + private final TokenStore redisTokenStore; - private final TokenEnhancer pigxTokenEnhancer; + private final TokenEnhancer tokenEnhancer; private final ObjectMapper objectMapper; @@ -81,19 +76,8 @@ public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdap @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) { - // 设置tokenStore 前缀相关 - String prefix = String.format(":%s%s", SecurityConstants.PIGX_PREFIX, SecurityConstants.OAUTH_PREFIX); - redisTokenStore.setPrefix(prefix); - // 设置区分key 条件 - redisTokenStore.setAuthenticationKeyGenerator(new DefaultAuthenticationKeyGenerator() { - @Override - public String extractKey(OAuth2Authentication authentication) { - return super.extractKey(authentication) + StrUtil.COLON + TenantContextHolder.getTenantId(); - } - }); - endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST).tokenStore(redisTokenStore) - .tokenEnhancer(pigxTokenEnhancer).userDetailsService(pigxUserDetailsService) + .tokenEnhancer(tokenEnhancer).userDetailsService(pigxUserDetailsService) .authorizationCodeServices(authorizationCodeServices).authenticationManager(authenticationManagerBean) .reuseRefreshTokens(false).pathMapping("/oauth/confirm_access", "/token/confirm_access") .exceptionTranslator(new PigxWebResponseExceptionTranslator()); diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/service/PigxRedisTokenStore.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/service/PigxRedisTokenStore.java deleted file mode 100644 index 28a725d95..000000000 --- a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/service/PigxRedisTokenStore.java +++ /dev/null @@ -1,502 +0,0 @@ -package com.pig4cloud.pigx.auth.service; - -import com.pig4cloud.pigx.common.core.constant.SecurityConstants; -import com.pig4cloud.pigx.common.data.tenant.TenantContextHolder; -import org.springframework.data.redis.connection.RedisConnection; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.connection.RedisStringCommands; -import org.springframework.data.redis.core.Cursor; -import org.springframework.data.redis.core.ScanOptions; -import org.springframework.data.redis.core.types.Expiration; -import org.springframework.security.oauth2.common.ExpiringOAuth2RefreshToken; -import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.common.OAuth2RefreshToken; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.token.AuthenticationKeyGenerator; -import org.springframework.security.oauth2.provider.token.DefaultAuthenticationKeyGenerator; -import org.springframework.security.oauth2.provider.token.TokenStore; -import org.springframework.security.oauth2.provider.token.store.redis.JdkSerializationStrategy; -import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore; -import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStoreSerializationStrategy; -import org.springframework.stereotype.Service; -import org.springframework.util.ClassUtils; -import org.springframework.util.ReflectionUtils; - -import java.lang.reflect.Method; -import java.util.*; - -/** - * @author lengleng - * @date 2020/9/3 - *

- * 重写默认tokenStore 保存 username and token 关系 - */ -@Service -public class PigxRedisTokenStore implements TokenStore { - - private static final String ACCESS = "access:"; - - private static final String AUTH_TO_ACCESS = "auth_to_access:"; - - private static final String AUTH = "auth:"; - - private static final String REFRESH_AUTH = "refresh_auth:"; - - private static final String ACCESS_TO_REFRESH = "access_to_refresh:"; - - private static final String REFRESH = "refresh:"; - - private static final String REFRESH_TO_ACCESS = "refresh_to_access:"; - - private static final String CLIENT_ID_TO_ACCESS = "client_id_to_access:"; - - private static final String UNAME_TO_ACCESS = "uname_to_access:"; - - private static final boolean springDataRedis_2_0 = ClassUtils.isPresent( - "org.springframework.data.redis.connection.RedisStandaloneConfiguration", - RedisTokenStore.class.getClassLoader()); - - private final RedisConnectionFactory connectionFactory; - - private AuthenticationKeyGenerator authenticationKeyGenerator = new DefaultAuthenticationKeyGenerator(); - - private RedisTokenStoreSerializationStrategy serializationStrategy = new JdkSerializationStrategy(); - - private String prefix = ""; - - private Method redisConnectionSet_2_0; - - public PigxRedisTokenStore(RedisConnectionFactory connectionFactory) { - this.connectionFactory = connectionFactory; - if (springDataRedis_2_0) { - this.loadRedisConnectionMethods_2_0(); - } - } - - 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); - } - - public void setAuthenticationKeyGenerator(AuthenticationKeyGenerator authenticationKeyGenerator) { - this.authenticationKeyGenerator = authenticationKeyGenerator; - } - - public void setSerializationStrategy(RedisTokenStoreSerializationStrategy serializationStrategy) { - this.serializationStrategy = serializationStrategy; - } - - public void setPrefix(String prefix) { - this.prefix = prefix; - } - - private void loadRedisConnectionMethods_2_0() { - this.redisConnectionSet_2_0 = ReflectionUtils.findMethod(RedisConnection.class, "set", byte[].class, - byte[].class); - } - - private RedisConnection getConnection() { - return connectionFactory.getConnection(); - } - - private byte[] serialize(Object object) { - return serializationStrategy.serialize(object); - } - - // 增加key 的多租户区分 - private byte[] serializeKey(String object) { - return serialize(TenantContextHolder.getTenantId() + prefix + object); - } - - private OAuth2AccessToken deserializeAccessToken(byte[] bytes) { - return serializationStrategy.deserialize(bytes, OAuth2AccessToken.class); - } - - private OAuth2Authentication deserializeAuthentication(byte[] bytes) { - return serializationStrategy.deserialize(bytes, OAuth2Authentication.class); - } - - private OAuth2RefreshToken deserializeRefreshToken(byte[] bytes) { - return serializationStrategy.deserialize(bytes, OAuth2RefreshToken.class); - } - - private byte[] serialize(String string) { - return serializationStrategy.serialize(string); - } - - private String deserializeString(byte[] bytes) { - return serializationStrategy.deserializeString(bytes); - } - - @Override - public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) { - String key = authenticationKeyGenerator.extractKey(authentication); - byte[] serializedKey = serializeKey(AUTH_TO_ACCESS + key); - byte[] bytes; - RedisConnection conn = getConnection(); - try { - bytes = conn.get(serializedKey); - } - finally { - conn.close(); - } - OAuth2AccessToken accessToken = deserializeAccessToken(bytes); - if (accessToken != null) { - OAuth2Authentication storedAuthentication = readAuthentication(accessToken.getValue()); - if ((storedAuthentication == null - || !key.equals(authenticationKeyGenerator.extractKey(storedAuthentication)))) { - // Keep the stores consistent (maybe the same user is - // represented by this authentication but the details have - // changed) - storeAccessToken(accessToken, authentication); - } - - } - return accessToken; - } - - @Override - public OAuth2Authentication readAuthentication(OAuth2AccessToken token) { - return readAuthentication(token.getValue()); - } - - @Override - public OAuth2Authentication readAuthentication(String token) { - byte[] bytes = null; - RedisConnection conn = getConnection(); - try { - bytes = conn.get(serializeKey(AUTH + token)); - } - finally { - conn.close(); - } - OAuth2Authentication auth = deserializeAuthentication(bytes); - return auth; - } - - @Override - public OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token) { - return readAuthenticationForRefreshToken(token.getValue()); - } - - public OAuth2Authentication readAuthenticationForRefreshToken(String token) { - RedisConnection conn = getConnection(); - try { - byte[] bytes = conn.get(serializeKey(REFRESH_AUTH + token)); - OAuth2Authentication auth = deserializeAuthentication(bytes); - return auth; - } - finally { - conn.close(); - } - } - - @Override - public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) { - byte[] serializedAccessToken = serialize(token); - byte[] serializedAuth = serialize(authentication); - byte[] accessKey = serializeKey(ACCESS + token.getValue()); - byte[] authKey = serializeKey(AUTH + token.getValue()); - byte[] authToAccessKey = serializeKey(AUTH_TO_ACCESS + authenticationKeyGenerator.extractKey(authentication)); - byte[] approvalKey = serializeKey(UNAME_TO_ACCESS + getApprovalKey(authentication)); - byte[] clientId = serializeKey(CLIENT_ID_TO_ACCESS + authentication.getOAuth2Request().getClientId()); - - RedisConnection conn = getConnection(); - try { - conn.openPipeline(); - if (springDataRedis_2_0) { - try { - this.redisConnectionSet_2_0.invoke(conn, accessKey, serializedAccessToken); - this.redisConnectionSet_2_0.invoke(conn, authKey, serializedAuth); - this.redisConnectionSet_2_0.invoke(conn, authToAccessKey, serializedAccessToken); - } - catch (Exception ex) { - throw new RuntimeException(ex); - } - } - else { - conn.set(accessKey, serializedAccessToken); - conn.set(authKey, serializedAuth); - conn.set(authToAccessKey, serializedAccessToken); - } - if (!authentication.isClientOnly()) { - conn.sAdd(approvalKey, serializedAccessToken); - } - conn.sAdd(clientId, serializedAccessToken); - if (token.getExpiration() != null) { - int seconds = token.getExpiresIn(); - conn.expire(accessKey, seconds); - conn.expire(authKey, seconds); - conn.expire(authToAccessKey, seconds); - conn.expire(clientId, seconds); - conn.expire(approvalKey, seconds); - } - OAuth2RefreshToken refreshToken = token.getRefreshToken(); - if (refreshToken != null && refreshToken.getValue() != null) { - byte[] refresh = serialize(token.getRefreshToken().getValue()); - byte[] auth = serialize(token.getValue()); - byte[] refreshToAccessKey = serializeKey(REFRESH_TO_ACCESS + token.getRefreshToken().getValue()); - byte[] accessToRefreshKey = serializeKey(ACCESS_TO_REFRESH + token.getValue()); - if (springDataRedis_2_0) { - try { - this.redisConnectionSet_2_0.invoke(conn, refreshToAccessKey, auth); - this.redisConnectionSet_2_0.invoke(conn, accessToRefreshKey, refresh); - } - catch (Exception ex) { - throw new RuntimeException(ex); - } - } - else { - conn.set(refreshToAccessKey, auth); - conn.set(accessToRefreshKey, refresh); - } - if (refreshToken instanceof ExpiringOAuth2RefreshToken) { - ExpiringOAuth2RefreshToken expiringRefreshToken = (ExpiringOAuth2RefreshToken) refreshToken; - Date expiration = expiringRefreshToken.getExpiration(); - if (expiration != null) { - int seconds = Long.valueOf((expiration.getTime() - System.currentTimeMillis()) / 1000L) - .intValue(); - conn.expire(refreshToAccessKey, seconds); - conn.expire(accessToRefreshKey, seconds); - } - } - } - - // KEY - byte[] key = serializeKey(String.format("%s%s", SecurityConstants.TOKEN_PREFIX, authentication.getName())); - // value - byte[] tokenVal = serialize(token.getValue()); - RedisStringCommands stringCommand = conn.stringCommands(); - stringCommand.set(key, tokenVal, Expiration.seconds(token.getExpiresIn()), - RedisStringCommands.SetOption.SET_IF_ABSENT); - conn.closePipeline(); - } - finally { - conn.close(); - } - } - - @Override - public void removeAccessToken(OAuth2AccessToken accessToken) { - removeAccessToken(accessToken.getValue()); - } - - @Override - public OAuth2AccessToken readAccessToken(String tokenValue) { - byte[] key = serializeKey(ACCESS + tokenValue); - byte[] bytes; - RedisConnection conn = getConnection(); - try { - bytes = conn.get(key); - } - finally { - conn.close(); - } - OAuth2AccessToken accessToken = deserializeAccessToken(bytes); - return accessToken; - } - - public void removeAccessToken(String tokenValue) { - byte[] accessKey = serializeKey(ACCESS + tokenValue); - byte[] authKey = serializeKey(AUTH + tokenValue); - byte[] accessToRefreshKey = serializeKey(ACCESS_TO_REFRESH + tokenValue); - RedisConnection conn = getConnection(); - try { - conn.openPipeline(); - conn.get(accessKey); - conn.get(authKey); - conn.del(accessKey); - conn.del(accessToRefreshKey); - // Don't remove the refresh token - it's up to the caller to do that - conn.del(authKey); - List results = conn.closePipeline(); - byte[] access = (byte[]) results.get(0); - byte[] auth = (byte[]) results.get(1); - - OAuth2Authentication authentication = deserializeAuthentication(auth); - if (authentication != null) { - String key = authenticationKeyGenerator.extractKey(authentication); - byte[] authToAccessKey = serializeKey(AUTH_TO_ACCESS + key); - byte[] unameKey = serializeKey(UNAME_TO_ACCESS + getApprovalKey(authentication)); - byte[] clientId = serializeKey(CLIENT_ID_TO_ACCESS + authentication.getOAuth2Request().getClientId()); - conn.openPipeline(); - conn.del(authToAccessKey); - conn.sRem(unameKey, access); - conn.sRem(clientId, access); - conn.del(serialize(ACCESS + key)); - - // keyName - byte[] keyName = serializeKey( - String.format("%s%s", SecurityConstants.TOKEN_PREFIX, authentication.getName())); - // 获取redis连接 - conn.del(keyName); - conn.closePipeline(); - } - } - finally { - conn.close(); - } - } - - @Override - public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) { - byte[] refreshKey = serializeKey(REFRESH + refreshToken.getValue()); - byte[] refreshAuthKey = serializeKey(REFRESH_AUTH + refreshToken.getValue()); - byte[] serializedRefreshToken = serialize(refreshToken); - RedisConnection conn = getConnection(); - try { - conn.openPipeline(); - if (springDataRedis_2_0) { - try { - this.redisConnectionSet_2_0.invoke(conn, refreshKey, serializedRefreshToken); - this.redisConnectionSet_2_0.invoke(conn, refreshAuthKey, serialize(authentication)); - } - catch (Exception ex) { - throw new RuntimeException(ex); - } - } - else { - conn.set(refreshKey, serializedRefreshToken); - conn.set(refreshAuthKey, serialize(authentication)); - } - if (refreshToken instanceof ExpiringOAuth2RefreshToken) { - ExpiringOAuth2RefreshToken expiringRefreshToken = (ExpiringOAuth2RefreshToken) refreshToken; - Date expiration = expiringRefreshToken.getExpiration(); - if (expiration != null) { - int seconds = Long.valueOf((expiration.getTime() - System.currentTimeMillis()) / 1000L).intValue(); - conn.expire(refreshKey, seconds); - conn.expire(refreshAuthKey, seconds); - } - } - conn.closePipeline(); - } - finally { - conn.close(); - } - } - - @Override - public OAuth2RefreshToken readRefreshToken(String tokenValue) { - byte[] key = serializeKey(REFRESH + tokenValue); - byte[] bytes = null; - RedisConnection conn = getConnection(); - try { - bytes = conn.get(key); - } - finally { - conn.close(); - } - OAuth2RefreshToken refreshToken = deserializeRefreshToken(bytes); - return refreshToken; - } - - @Override - public void removeRefreshToken(OAuth2RefreshToken refreshToken) { - removeRefreshToken(refreshToken.getValue()); - } - - public void removeRefreshToken(String tokenValue) { - byte[] refreshKey = serializeKey(REFRESH + tokenValue); - byte[] refreshAuthKey = serializeKey(REFRESH_AUTH + tokenValue); - byte[] refresh2AccessKey = serializeKey(REFRESH_TO_ACCESS + tokenValue); - byte[] access2RefreshKey = serializeKey(ACCESS_TO_REFRESH + tokenValue); - RedisConnection conn = getConnection(); - try { - conn.openPipeline(); - conn.del(refreshKey); - conn.del(refreshAuthKey); - conn.del(refresh2AccessKey); - conn.del(access2RefreshKey); - conn.closePipeline(); - } - finally { - conn.close(); - } - } - - @Override - public void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken) { - removeAccessTokenUsingRefreshToken(refreshToken.getValue()); - } - - private void removeAccessTokenUsingRefreshToken(String refreshToken) { - byte[] key = serializeKey(REFRESH_TO_ACCESS + refreshToken); - List results = null; - RedisConnection conn = getConnection(); - try { - conn.openPipeline(); - conn.get(key); - conn.del(key); - results = conn.closePipeline(); - } - finally { - conn.close(); - } - byte[] bytes = (byte[]) results.get(0); - String accessToken = deserializeString(bytes); - if (accessToken != null) { - removeAccessToken(accessToken); - } - } - - private List getByteLists(byte[] approvalKey, RedisConnection conn) { - List byteList; - Long size = conn.sCard(approvalKey); - byteList = new ArrayList(size.intValue()); - Cursor cursor = conn.sScan(approvalKey, ScanOptions.NONE); - while (cursor.hasNext()) { - byteList.add(cursor.next()); - } - return byteList; - } - - @Override - public Collection findTokensByClientIdAndUserName(String clientId, String userName) { - byte[] approvalKey = serializeKey(UNAME_TO_ACCESS + getApprovalKey(clientId, userName)); - List byteList; - RedisConnection conn = getConnection(); - try { - byteList = getByteLists(approvalKey, conn); - } - finally { - conn.close(); - } - if (byteList == null || byteList.size() == 0) { - return Collections.emptySet(); - } - List accessTokens = new ArrayList(byteList.size()); - for (byte[] bytes : byteList) { - OAuth2AccessToken accessToken = deserializeAccessToken(bytes); - accessTokens.add(accessToken); - } - return Collections.unmodifiableCollection(accessTokens); - } - - @Override - public Collection findTokensByClientId(String clientId) { - byte[] key = serializeKey(CLIENT_ID_TO_ACCESS + clientId); - List byteList; - RedisConnection conn = getConnection(); - try { - byteList = getByteLists(key, conn); - } - finally { - conn.close(); - } - if (byteList == null || byteList.size() == 0) { - return Collections.emptySet(); - } - List accessTokens = new ArrayList(byteList.size()); - for (byte[] bytes : byteList) { - OAuth2AccessToken accessToken = deserializeAccessToken(bytes); - accessTokens.add(accessToken); - } - return Collections.unmodifiableCollection(accessTokens); - } - -} diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/service/PigxTokenDealServiceImpl.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/service/PigxTokenDealServiceImpl.java index 16702ec1e..8237a9c7b 100644 --- a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/service/PigxTokenDealServiceImpl.java +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/service/PigxTokenDealServiceImpl.java @@ -1,12 +1,11 @@ package com.pig4cloud.pigx.auth.service; -import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.pig4cloud.pigx.common.core.constant.CacheConstants; import com.pig4cloud.pigx.common.core.constant.SecurityConstants; +import com.pig4cloud.pigx.common.core.util.KeyStrResolver; import com.pig4cloud.pigx.common.core.util.R; -import com.pig4cloud.pigx.common.data.tenant.TenantContextHolder; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.cache.CacheManager; @@ -16,7 +15,6 @@ import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ScanOptions; import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializer; -import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2RefreshToken; import org.springframework.security.oauth2.provider.OAuth2Authentication; @@ -26,6 +24,7 @@ import org.springframework.stereotype.Service; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; /** * @author lengleng @@ -44,6 +43,8 @@ public class PigxTokenDealServiceImpl { private final TokenStore tokenStore; + private final KeyStrResolver keyStrResolver; + /** * 删除 请求令牌 和 刷新令牌 * @param token 请求令牌 @@ -75,23 +76,20 @@ public class PigxTokenDealServiceImpl { * @param username 用户名 * @return R */ - public R queryTokenByUsername(Page page, String username) { - String key = String.format("%s:%s%s%s%s", TenantContextHolder.getTenantId(), SecurityConstants.PIGX_PREFIX, - SecurityConstants.OAUTH_PREFIX, SecurityConstants.TOKEN_PREFIX, username); - // 根据用户名查询token - redisTemplate.setValueSerializer(new StringRedisSerializer()); - Object token = redisTemplate.opsForValue().get(key); + public R queryTokenByUsername(Page page, String username) { + String key = keyStrResolver.extract( + SecurityConstants.PIGX_PREFIX + SecurityConstants.OAUTH_PREFIX + "uname_to_access:", StrUtil.COLON); - // 根据token 查询详细信息 - String keyVal = String.format("%s:%s%s%s%s", TenantContextHolder.getTenantId(), SecurityConstants.PIGX_PREFIX, - SecurityConstants.OAUTH_PREFIX, "access:", token); + Object collect = redisTemplate.keys("*:" + username).stream().filter(k -> ((String) k).contains(key)) + .flatMap(k -> { + redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer()); + return redisTemplate.opsForSet().members(k).stream(); + }).collect(Collectors.toList()); - redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer()); - Object o = redisTemplate.opsForValue().get(keyVal); - - if (o != null) { - page.setRecords(CollUtil.newArrayList(o)); - page.setTotal(1L); + if (collect instanceof List) { + List objSet = (List) collect; + page.setRecords(objSet); + page.setTotal(objSet.size()); } return R.ok(page); } @@ -103,10 +101,9 @@ public class PigxTokenDealServiceImpl { */ public R queryToken(Page page) { // 根据分页参数获取对应数据 - String key = String.format("%s:%s%s%s*", TenantContextHolder.getTenantId(), SecurityConstants.PIGX_PREFIX, - SecurityConstants.OAUTH_PREFIX, "access:"); + String key = keyStrResolver.extract(SecurityConstants.PIGX_PREFIX + SecurityConstants.OAUTH_PREFIX + "access:*", + StrUtil.COLON); List pages = findKeysForPage(key, page.getCurrent(), page.getSize()); - redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer()); page.setRecords(redisTemplate.opsForValue().multiGet(pages)); page.setTotal(redisTemplate.keys(key).size()); diff --git a/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/util/KeyStrResolver.java b/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/util/KeyStrResolver.java new file mode 100644 index 000000000..2b7926889 --- /dev/null +++ b/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/util/KeyStrResolver.java @@ -0,0 +1,19 @@ +package com.pig4cloud.pigx.common.core.util; + +/** + * @author lengleng + * @date 2020/9/29 + *

+ * 字符串处理,方便其他模块解耦处理 + */ +public interface KeyStrResolver { + + /** + * 字符串加工 + * @param in 输入字符串 + * @param split 分割符 + * @return 输出字符串 + */ + String extract(String in, String split); + +} diff --git a/pigx-common/pigx-common-data/src/main/java/com/pig4cloud/pigx/common/data/resolver/TenantKeyStrResolver.java b/pigx-common/pigx-common-data/src/main/java/com/pig4cloud/pigx/common/data/resolver/TenantKeyStrResolver.java new file mode 100644 index 000000000..77e80778a --- /dev/null +++ b/pigx-common/pigx-common-data/src/main/java/com/pig4cloud/pigx/common/data/resolver/TenantKeyStrResolver.java @@ -0,0 +1,25 @@ +package com.pig4cloud.pigx.common.data.resolver; + +import com.pig4cloud.pigx.common.core.util.KeyStrResolver; +import com.pig4cloud.pigx.common.data.tenant.TenantContextHolder; + +/** + * @author lengleng + * @date 2020/9/29 + *

+ * 租户字符串处理(方便其他模块获取) + */ +public class TenantKeyStrResolver implements KeyStrResolver { + + /** + * 传入字符串增加 租户编号:in + * @param in 输入字符串 + * @param split 分割符 + * @return + */ + @Override + public String extract(String in, String split) { + return TenantContextHolder.getTenantId() + split + in; + } + +} diff --git a/pigx-common/pigx-common-data/src/main/resources/META-INF/spring.factories b/pigx-common/pigx-common-data/src/main/resources/META-INF/spring.factories index 92bd2f9e4..679dfbecc 100644 --- a/pigx-common/pigx-common-data/src/main/resources/META-INF/spring.factories +++ b/pigx-common/pigx-common-data/src/main/resources/META-INF/spring.factories @@ -6,4 +6,5 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.pig4cloud.pigx.common.data.tenant.TenantContextHolderFilter,\ com.pig4cloud.pigx.common.data.tenant.PigxTenantConfiguration,\ com.pig4cloud.pigx.common.data.mybatis.MybatisPlusConfiguration,\ - com.pig4cloud.pigx.common.data.resolver.WebMvcConfig \ No newline at end of file + com.pig4cloud.pigx.common.data.resolver.TenantKeyStrResolver,\ + com.pig4cloud.pigx.common.data.resolver.WebMvcConfig diff --git a/pigx-common/pigx-common-security/pom.xml b/pigx-common/pigx-common-security/pom.xml index 5e2d4d6d2..2592a3b19 100644 --- a/pigx-common/pigx-common-security/pom.xml +++ b/pigx-common/pigx-common-security/pom.xml @@ -53,5 +53,10 @@ org.aspectj aspectjrt + + + org.springframework.data + spring-data-redis + diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/annotation/EnablePigxResourceServer.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/annotation/EnablePigxResourceServer.java index d951467b8..02dcd7aad 100644 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/annotation/EnablePigxResourceServer.java +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/annotation/EnablePigxResourceServer.java @@ -40,4 +40,10 @@ import java.lang.annotation.*; @Import({ PigxResourceServerAutoConfiguration.class, PigxSecurityBeanDefinitionRegistrar.class }) public @interface EnablePigxResourceServer { + /** + * 是否开启本地模式 + * @return true + */ + boolean isLocal() default true; + } diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxLocalResourceServerConfigurerAdapter.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxLocalResourceServerConfigurerAdapter.java new file mode 100644 index 000000000..d29292224 --- /dev/null +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxLocalResourceServerConfigurerAdapter.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2018-2025, lengleng All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * Neither the name of the pig4cloud.com developer nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * Author: lengleng (wangiegie@gmail.com) + */ + +package com.pig4cloud.pigx.common.security.component; + +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; +import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; +import org.springframework.security.oauth2.provider.authentication.TokenExtractor; +import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; +import org.springframework.security.web.AuthenticationEntryPoint; + +/** + * @author lengleng + * @date 2020/9/30 + *

+ * 支持本地模式 [不仅过认证中 CheckToken]的的资源服务器配置 + */ +@Slf4j +public class PigxLocalResourceServerConfigurerAdapter extends ResourceServerConfigurerAdapter { + + @Autowired + protected AuthenticationEntryPoint resourceAuthExceptionEntryPoint; + + @Autowired + private PermitAllUrlProperties permitAllUrlProperties; + + @Autowired + private TokenExtractor tokenExtractor; + + @Autowired + private ResourceServerTokenServices resourceServerTokenServices; + + /** + * 默认的配置,对外暴露 + * @param httpSecurity + */ + @Override + @SneakyThrows + public void configure(HttpSecurity httpSecurity) { + // 允许使用iframe 嵌套,避免swagger-ui 不被加载的问题 + httpSecurity.headers().frameOptions().disable(); + ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry registry = httpSecurity + .authorizeRequests(); + permitAllUrlProperties.getIgnoreUrls().forEach(url -> registry.antMatchers(url).permitAll()); + registry.anyRequest().authenticated().and().csrf().disable(); + } + + @Override + public void configure(ResourceServerSecurityConfigurer resources) { + resources.authenticationEntryPoint(resourceAuthExceptionEntryPoint).tokenExtractor(tokenExtractor) + .tokenServices(resourceServerTokenServices); + } + +} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxLocalResourceServerTokenServices.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxLocalResourceServerTokenServices.java new file mode 100644 index 000000000..218e6301a --- /dev/null +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxLocalResourceServerTokenServices.java @@ -0,0 +1,35 @@ +package com.pig4cloud.pigx.common.security.component; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Primary; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; +import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.stereotype.Service; + +/** + * @author lengleng + * @date 2020/9/29 + */ +@Primary +@Service +@RequiredArgsConstructor +public class PigxLocalResourceServerTokenServices implements ResourceServerTokenServices { + + private final TokenStore tokenStore; + + @Override + public OAuth2Authentication loadAuthentication(String accessToken) + throws AuthenticationException, InvalidTokenException { + return tokenStore.readAuthentication(accessToken); + } + + @Override + public OAuth2AccessToken readAccessToken(String accessToken) { + throw new UnsupportedOperationException("Not supported: read access token"); + } + +} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxSecurityBeanDefinitionRegistrar.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxSecurityBeanDefinitionRegistrar.java index 4c861fe69..929d7fe71 100644 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxSecurityBeanDefinitionRegistrar.java +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxSecurityBeanDefinitionRegistrar.java @@ -18,6 +18,7 @@ package com.pig4cloud.pigx.common.security.component; import com.pig4cloud.pigx.common.core.constant.SecurityConstants; +import com.pig4cloud.pigx.common.security.annotation.EnablePigxResourceServer; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.GenericBeanDefinition; @@ -44,7 +45,16 @@ public class PigxSecurityBeanDefinitionRegistrar implements ImportBeanDefinition } GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); - beanDefinition.setBeanClass(PigxResourceServerConfigurerAdapter.class); + + Boolean isLocal = (Boolean) metadata.getAnnotationAttributes(EnablePigxResourceServer.class.getName()) + .get("isLocal"); + if (isLocal) { + beanDefinition.setBeanClass(PigxLocalResourceServerConfigurerAdapter.class); + } + else { + beanDefinition.setBeanClass(PigxResourceServerConfigurerAdapter.class); + } + registry.registerBeanDefinition(SecurityConstants.RESOURCE_SERVER_CONFIGURER, beanDefinition); } diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxTokenStoreAutoConfiguration.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxTokenStoreAutoConfiguration.java new file mode 100644 index 000000000..35a8584e8 --- /dev/null +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxTokenStoreAutoConfiguration.java @@ -0,0 +1,48 @@ +package com.pig4cloud.pigx.common.security.component; + +import cn.hutool.core.util.StrUtil; +import com.pig4cloud.pigx.common.core.constant.SecurityConstants; +import com.pig4cloud.pigx.common.core.util.KeyStrResolver; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.token.DefaultAuthenticationKeyGenerator; +import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore; + +/** + * @author lengleng + * @date 2020/9/29 + *

+ * redis token store 自动配置 + */ +@RequiredArgsConstructor +@Configuration(proxyBeanMethods = false) +public class PigxTokenStoreAutoConfiguration { + + private final KeyStrResolver resolver; + + private final RedisConnectionFactory connectionFactory; + + @Bean + public TokenStore tokenStore() { + RedisTokenStore tokenStore = new RedisTokenStore(connectionFactory); + tokenStore.setPrefix( + resolver.extract(SecurityConstants.PIGX_PREFIX + SecurityConstants.OAUTH_PREFIX, StrUtil.COLON)); + + tokenStore.setAuthenticationKeyGenerator(new DefaultAuthenticationKeyGenerator() { + @Override + public String extractKey(OAuth2Authentication authentication) { + if (resolver == null) { + return super.extractKey(authentication); + } + // 增加租户隔离部分 租户ID:原生计算值 + return resolver.extract(super.extractKey(authentication), StrUtil.COLON); + } + }); + return tokenStore; + } + +} diff --git a/pigx-common/pigx-common-security/src/main/resources/META-INF/spring.factories b/pigx-common/pigx-common-security/src/main/resources/META-INF/spring.factories index 685830eee..21f3f2fc7 100644 --- a/pigx-common/pigx-common-security/src/main/resources/META-INF/spring.factories +++ b/pigx-common/pigx-common-security/src/main/resources/META-INF/spring.factories @@ -1,6 +1,7 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.pig4cloud.pigx.common.security.component.PigxTokenEnhancer,\ com.pig4cloud.pigx.common.security.component.PigxSecurityInnerAspect,\ + com.pig4cloud.pigx.common.security.component.PigxTokenStoreAutoConfiguration,\ com.pig4cloud.pigx.common.security.listener.AuthenticationFailureEventListener,\ com.pig4cloud.pigx.common.security.listener.AuthenticationSuccessEventListener diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/config/DynamicRouteInitRunner.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/config/DynamicRouteInitRunner.java index 2c44755be..e2c6cf6fa 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/config/DynamicRouteInitRunner.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/config/DynamicRouteInitRunner.java @@ -58,7 +58,7 @@ public class DynamicRouteInitRunner { @Async @Order - @EventListener({WebServerInitializedEvent.class, DynamicRouteInitEvent.class}) + @EventListener({ WebServerInitializedEvent.class, DynamicRouteInitEvent.class }) public void initRoute() { redisTemplate.delete(CacheConstants.ROUTE_KEY); log.info("开始初始化网关路由"); @@ -85,7 +85,6 @@ public class DynamicRouteInitRunner { log.debug("初始化网关路由结束 "); } - /** * redis 监听配置,监听 gateway_redis_route_reload_topic,重新加载Redis * @param redisConnectionFactory redis 配置 @@ -101,4 +100,5 @@ public class DynamicRouteInitRunner { }, new ChannelTopic(CacheConstants.ROUTE_REDIS_RELOAD_TOPIC)); return container; } + } diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysTokenController.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysTokenController.java index bb0ee273d..c1f9d4e76 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysTokenController.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysTokenController.java @@ -47,6 +47,7 @@ public class SysTokenController { */ @GetMapping("/page") public R getTokenPage(@RequestParam Map params) { + // 获取请求的 return remoteTokenService.getTokenPage(params, SecurityConstants.FROM_IN); }