05.oauth2中RedisTokenStore使用redis cluster的問題

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:[email protected]
  • @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 [email protected]
  • @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;
    }

    /**

    }

    @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 [email protected]
  • @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));
    });
}

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章