spring-cloud-starter-oauth2项目使用redis-cluster存储token信息报错
项目https://gitee.com/owenwangwen/open-capacity-platform/tree/master/oauth-center/auth-server
application.yml文件
spring:
application:
name: auth-server-with-db
http:
encoding:
charset: utf8
force: true
enabled: true
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: oracle.jdbc.OracleDriver
url: jdbc:oracle:thin:@127.0.0.1:1521:orcl
username: scott
password: tiger
filters: stat,wall
redis:
database: 1
cluster:
nodes: 130.75.131.237:7000,130.75.131.238:7000,130.75.131.239:7000,130.75.131.237:7001,130.75.131.238:7001,130.75.131.239:7001
timeout: 1000 # 连接超时时间(毫秒)
pool:
max-active: 10 # 连接池最大连接数(使用负值表示没有限制)
max-idle: 8 # 连接池中的最大空闲连接
min-idle: 2 # 连接池中的最小空闲连接
max-wait: 100 # 连接池最大阻塞等待时间(使用负值表示没有限制)
使用RedisTokenStore连接redis-cluster,存储token的信息
@Bean
@ConditionalOnProperty(prefix="security.oauth2.token.store",name="type" ,havingValue="redis" ,matchIfMissing=true)
public TokenStore redisTokenStore(){
return new RedisTokenStore( redisTemplate.getConnectionFactory() ) ;
}
此处报错Pipeline is currently not supported for JedisClusterConnection,根据RedisTokenStore修改源码RedisTemplateStore,支持oauth2的token的集群存储
package com.neusoft.unieap.sso.oauth2.token.store;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
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.util.Assert;
/**
- @author 作者 owen E-mail:624191343@qq.com
- @version 创建时间:2017年12月18日 下午4:54:03
- 类说明
- redis集群存储token
*/
public class RedisTemplateTokenStore 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 RedisTemplate<String,Object> redisTemplate ;
public RedisTemplate<String,Object> getRedisTemplate() {
return redisTemplate;
}
public void setRedisTemplate(RedisTemplate<String,Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
private AuthenticationKeyGenerator authenticationKeyGenerator = new DefaultAuthenticationKeyGenerator();
public void setAuthenticationKeyGenerator(AuthenticationKeyGenerator authenticationKeyGenerator) {
this.authenticationKeyGenerator = authenticationKeyGenerator;
}
public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {
String key = authenticationKeyGenerator.extractKey(authentication);
OAuth2AccessToken accessToken = (OAuth2AccessToken) redisTemplate.opsForValue().get(AUTH_TO_ACCESS+key);
if (accessToken != null
&& !key.equals(authenticationKeyGenerator.extractKey(readAuthentication(accessToken.getValue())))) {
// Keep the stores consistent (maybe the same user is represented by this authentication but the details
// have changed)
storeAccessToken(accessToken, authentication);
}
return accessToken;
}
public OAuth2Authentication readAuthentication(OAuth2AccessToken token) {
return readAuthentication(token.getValue());
}
public OAuth2Authentication readAuthentication(String token) {
return (OAuth2Authentication) this.redisTemplate.opsForValue().get(AUTH + token);
}
public OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token) {
return readAuthenticationForRefreshToken(token.getValue());
}
public OAuth2Authentication readAuthenticationForRefreshToken(String token) {
return (OAuth2Authentication) this.redisTemplate.opsForValue().get( REFRESH_AUTH+token);
}
public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
this.redisTemplate.opsForValue().set(ACCESS+ token.getValue(), token);
this.redisTemplate.opsForValue().set(AUTH +token.getValue(), authentication);
this.redisTemplate.opsForValue().set(AUTH_TO_ACCESS+authenticationKeyGenerator.extractKey(authentication), token);
if (!authentication.isClientOnly()) {
redisTemplate.opsForList().rightPush(UNAME_TO_ACCESS+getApprovalKey(authentication), token) ;
}
redisTemplate.opsForList().rightPush(CLIENT_ID_TO_ACCESS+authentication.getOAuth2Request().getClientId(), token) ;
if (token.getExpiration() != null) {
int seconds = token.getExpiresIn();
redisTemplate.expire(ACCESS+ token.getValue(), seconds, TimeUnit.SECONDS) ;
redisTemplate.expire(AUTH+ token.getValue(), seconds, TimeUnit.SECONDS) ;
redisTemplate.expire(AUTH_TO_ACCESS+ authenticationKeyGenerator.extractKey(authentication), seconds, TimeUnit.SECONDS) ;
redisTemplate.expire(CLIENT_ID_TO_ACCESS+authentication.getOAuth2Request().getClientId(), seconds, TimeUnit.SECONDS) ;
redisTemplate.expire(UNAME_TO_ACCESS+ getApprovalKey(authentication), seconds, TimeUnit.SECONDS) ;
}
if (token.getRefreshToken() != null && token.getRefreshToken().getValue() != null) {
this.redisTemplate.opsForValue().set( REFRESH_TO_ACCESS+ token.getRefreshToken().getValue(), token.getValue());
this.redisTemplate.opsForValue().set(ACCESS_TO_REFRESH+token.getValue(), token.getRefreshToken().getValue());
}
}
private String getApprovalKey(OAuth2Authentication authentication) {
String userName = authentication.getUserAuthentication() == null ? "" : authentication.getUserAuthentication()
.getName();
return getApprovalKey(authentication.getOAuth2Request().getClientId(), userName);
}
private String getApprovalKey(String clientId, String userName) {
return clientId + (userName==null ? "" : ":" + userName);
}
public void removeAccessToken(OAuth2AccessToken accessToken) {
removeAccessToken(accessToken.getValue());
}
public OAuth2AccessToken readAccessToken(String tokenValue) {
return (OAuth2AccessToken) this.redisTemplate.opsForValue().get(ACCESS+tokenValue);
}
public void removeAccessToken(String tokenValue) {
OAuth2AccessToken removed = (OAuth2AccessToken) redisTemplate.opsForValue().get(ACCESS+tokenValue);
// Don't remove the refresh token - it's up to the caller to do that
OAuth2Authentication authentication = (OAuth2Authentication) this.redisTemplate.opsForValue().get(AUTH+tokenValue);
this.redisTemplate.delete(AUTH+tokenValue);
redisTemplate.delete(ACCESS+tokenValue);
this.redisTemplate.delete(ACCESS_TO_REFRESH +tokenValue);
if (authentication != null) {
this.redisTemplate.delete(AUTH_TO_ACCESS+authenticationKeyGenerator.extractKey(authentication));
String clientId = authentication.getOAuth2Request().getClientId();
// redisTemplate.opsForList().rightPush("UNAME_TO_ACCESS:"+getApprovalKey(authentication), token) ;
redisTemplate.opsForList().leftPop(UNAME_TO_ACCESS+getApprovalKey(clientId, authentication.getName()));
redisTemplate.opsForList().leftPop(CLIENT_ID_TO_ACCESS+clientId);
this.redisTemplate.delete(AUTH_TO_ACCESS+authenticationKeyGenerator.extractKey(authentication));
}
}
public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) {
this.redisTemplate.opsForValue().set(REFRESH+refreshToken.getValue(), refreshToken);
this.redisTemplate.opsForValue().set( REFRESH_AUTH + refreshToken.getValue(), authentication);
}
public OAuth2RefreshToken readRefreshToken(String tokenValue) {
return (OAuth2RefreshToken) this.redisTemplate.opsForValue().get(REFRESH+tokenValue);
}
public void removeRefreshToken(OAuth2RefreshToken refreshToken) {
removeRefreshToken(refreshToken.getValue());
}
public void removeRefreshToken(String tokenValue) {
this.redisTemplate.delete( REFRESH + tokenValue);
this.redisTemplate.delete( REFRESH_AUTH + tokenValue);
this.redisTemplate.delete(REFRESH_TO_ACCESS +tokenValue);
}
public void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken) {
removeAccessTokenUsingRefreshToken(refreshToken.getValue());
}
private void removeAccessTokenUsingRefreshToken(String refreshToken) {
String token = (String) this.redisTemplate.opsForValue().get( REFRESH_TO_ACCESS +refreshToken) ;
if (token != null) {
redisTemplate.delete(ACCESS+ token);
}
}
public Collection<OAuth2AccessToken> findTokensByClientIdAndUserName(String clientId, String userName) {
List<Object> result = redisTemplate.opsForList().range(UNAME_TO_ACCESS+ getApprovalKey(clientId, userName), 0, -1);
if (result == null || result.size() == 0) {
return Collections.<OAuth2AccessToken> emptySet();
}
List<OAuth2AccessToken> accessTokens = new ArrayList<OAuth2AccessToken>(result.size());
for(Iterator<Object> it = result.iterator();it.hasNext();){
OAuth2AccessToken accessToken = (OAuth2AccessToken) it.next();
accessTokens.add(accessToken);
}
return Collections.<OAuth2AccessToken> unmodifiableCollection(accessTokens);
}
public Collection<OAuth2AccessToken> findTokensByClientId(String clientId) {
List<Object> result = redisTemplate.opsForList().range((CLIENT_ID_TO_ACCESS+clientId), 0, -1);
if (result == null || result.size() == 0) {
return Collections.<OAuth2AccessToken> emptySet();
}
List<OAuth2AccessToken> accessTokens = new ArrayList<OAuth2AccessToken>(result.size());
for(Iterator<Object> it = result.iterator();it.hasNext();){
OAuth2AccessToken accessToken = (OAuth2AccessToken) it.next();
accessTokens.add(accessToken);
}
return Collections.<OAuth2AccessToken> unmodifiableCollection(accessTokens);
}
}
重写授权码持久化,解决token-info_url为zuul地址问题,授权码需要持久化
security:
user:
password: 123456
oauth2:
sso:
login-path: /login
client:
client-id: owen
client-secret: owen
user-authorization-uri: http://127.0.0.1:8000/auth/oauth/authorize
access-token-uri: http://127.0.0.1:8000/auth/oauth/token
resource:
user-info-uri: http://127.0.0.1:8000/auth/users #返回认证服务器检查
prefer-token-info: false
token-info-uri: http://127.0.0.1:8000/auth/oauth/check_token
prefer-token-info: true
package com.open.capacity.server.oauth2.code;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.code.RandomValueAuthorizationCodeServices;
import java.util.concurrent.TimeUnit;
/**
- @author owen 624191343@qq.com
- @version 创建时间:2017年11月12日 上午22:57:51
-
JdbcAuthorizationCodeServices替换
*/
public class RedisAuthorizationCodeServices extends RandomValueAuthorizationCodeServices {private RedisTemplate<String, Object> redisTemplate;
public RedisTemplate<String, Object> getRedisTemplate() {
return redisTemplate;
}public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}/**
- 替换JdbcAuthorizationCodeServices的存储策略
-
将存储code到redis,并设置过期时间,10分钟<br>br/>*/
@Override
protected void store(String code, OAuth2Authentication authentication) {redisTemplate.opsForValue().set(redisKey(code), authentication, 10, TimeUnit.MINUTES);
}
@Override
protected OAuth2Authentication remove(final String code) {String codeKey = redisKey(code); OAuth2Authentication token = (OAuth2Authentication) redisTemplate.opsForValue().get(codeKey); this.redisTemplate.delete(codeKey); return token;
}
/**
- redis中 code key的前缀
- @param code
- @return
*/
private String redisKey(String code) {
return "oauth:code:" + code;
}
}
package com.open.capacity.server.oauth2.client;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.NoSuchClientException;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.util.CollectionUtils;
import javax.sql.DataSource;
import java.util.List;
/**
- @author owen 624191343@qq.com
- @version 创建时间:2017年11月12日 上午22:57:51
- 类说明
- 将oauth_client_details表数据缓存到redis,这里做个缓存优化
*/
public class RedisClientDetailsService extends JdbcClientDetailsService {
/**
* 缓存client的redis key,这里是hash结构存储
*/
private static final String CACHE_CLIENT_KEY = "oauth_client_details";
private Logger logger = LoggerFactory.getLogger(RedisClientDetailsService.class);
private RedisTemplate<String, Object> redisTemplate;
public RedisTemplate<String, Object> getRedisTemplate() {
return redisTemplate;
}
public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public RedisClientDetailsService(DataSource dataSource) {
super(dataSource);
}
@Override
public ClientDetails loadClientByClientId(String clientId) throws InvalidClientException {
ClientDetails clientDetails = null;
// 先从redis获取
String value = (String) redisTemplate.boundHashOps(CACHE_CLIENT_KEY).get(clientId);
if (StringUtils.isBlank(value)) {
clientDetails = cacheAndGetClient(clientId);
} else {
clientDetails = JSONObject.parseObject(value, BaseClientDetails.class);
}
return clientDetails;
}
/**
* 缓存client并返回client
*
* @param clientId
* @return
*/
private ClientDetails cacheAndGetClient(String clientId) {
// 从数据库读取
ClientDetails clientDetails = null;
try {
clientDetails = super.loadClientByClientId(clientId);
if (clientDetails != null) {
// 写入redis缓存
redisTemplate.boundHashOps(CACHE_CLIENT_KEY).put(clientId, JSONObject.toJSONString(clientDetails));
logger.info("缓存clientId:{},{}", clientId, clientDetails);
}
} catch (NoSuchClientException e) {
logger.info("clientId:{},{}", clientId, clientId);
} catch (InvalidClientException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return clientDetails;
}
@Override
public void updateClientDetails(ClientDetails clientDetails) throws NoSuchClientException {
super.updateClientDetails(clientDetails);
cacheAndGetClient(clientDetails.getClientId());
}
@Override
public void updateClientSecret(String clientId, String secret) throws NoSuchClientException {
super.updateClientSecret(clientId, secret);
cacheAndGetClient(clientId);
}
@Override
public void removeClientDetails(String clientId) throws NoSuchClientException {
super.removeClientDetails(clientId);
removeRedisCache(clientId);
}
/**
* 删除redis缓存
*
* @param clientId
*/
private void removeRedisCache(String clientId) {
redisTemplate.boundHashOps(CACHE_CLIENT_KEY).delete(clientId);
}
/**
* 将oauth_client_details全表刷入redis
*/
public void loadAllClientToCache() {
if (redisTemplate.hasKey(CACHE_CLIENT_KEY)) {
return;
}
logger.info("将oauth_client_details全表刷入redis");
List<ClientDetails> list = super.listClientDetails();
if (CollectionUtils.isEmpty(list)) {
logger.error("oauth_client_details表数据为空,请检查");
return;
}
list.parallelStream().forEach(client -> {
redisTemplate.boundHashOps(CACHE_CLIENT_KEY).put(client.getClientId(), JSONObject.toJSONString(client));
});
}
}
转载于:https://blog.51cto.com/13005375/2052587