Oauth2使用redisTokenStore实现单点登录,多点登陆

前言:
最近开发认证服务的时候,发现使用redisTokenStore之后,多点登陆获取到的token跟第一次获取到的相同;不满足单点登录需求;故写此文章记录处理方法

1.追踪源码,搜寻资料;TokenService默认实现为DefaultTokenServices;查看源代码发现,DefaultTokenServices类的createAccessToken方法是创建token的;这里他创建之前,会根据OAuth2Authentication 去tokenStore中获取存储的token。由于同一个用户的OAuth2Authentication 的内容是相同的,故多次登录,从redis中取的token都是相同的;如果获取到了存在的token则会返回获取到的,否则才会重新创建;

DefaultTokenServices类

// DefaultTokenServices类中的创建token方法
public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
 3                 //从redis中获取登陆信息
 4         OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
 5         OAuth2RefreshToken refreshToken = null;
 6         if (existingAccessToken != null) {
 7             if (existingAccessToken.isExpired()) {
 8                 if (existingAccessToken.getRefreshToken() != null) {
 9                     refreshToken = existingAccessToken.getRefreshToken();
10                     // The token store could remove the refresh token when the
11                     // access token is removed, but we want to
12                     // be sure...
13                     tokenStore.removeRefreshToken(refreshToken);
14                 }
15                 tokenStore.removeAccessToken(existingAccessToken);
16             }
17             else {
18                 // Re-store the access token in case the authentication has changed
19                 tokenStore.storeAccessToken(existingAccessToken, authentication);
20                 return existingAccessToken;
21             }
22         }
23 
29         if (refreshToken == null) {
30             refreshToken = createRefreshToken(authentication);
31         }
34         else if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
35             ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken;
36             if (System.currentTimeMillis() > expiring.getExpiration().getTime()) {
37                 refreshToken = createRefreshToken(authentication);
38             }
39         }
40 
41         OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
42         tokenStore.storeAccessToken(accessToken, authentication);
44         refreshToken = accessToken.getRefreshToken();
45         if (refreshToken != null) {
46             tokenStore.storeRefreshToken(refreshToken, authentication);
47         }
48         return accessToken;
49 
50     }    

RedisTokenStore中的获取存储在redis中的方法

@Override
 2     public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {
 5         String key = authenticationKeyGenerator.extractKey(authentication);
 6         byte[] serializedKey = serializeKey(AUTH_TO_ACCESS + key);
 7         byte[] bytes = null;
 8         RedisConnection conn = getConnection();
 9         try {
10             bytes = conn.get(serializedKey);
11         } finally {
12             conn.close();
13         }
14         OAuth2AccessToken accessToken = deserializeAccessToken(bytes);
15         if (accessToken != null) {
16             OAuth2Authentication storedAuthentication = readAuthentication(accessToken.getValue());
17             if ((storedAuthentication == null || !key.equals(authenticationKeyGenerator.extractKey(storedAuthentication)))) {
21                 storeAccessToken(accessToken, authentication);
22             }
23 
24         }
25         return accessToken;
26     }

要实现的功能如下:

  • 同已用户每次获取token,获取到的都是同一个token,只有token失效后才会获取新token。
  • 同一用户每次获取token都生成一个完成周期的token并且保证每次生成的token都能够使用(多点登录)。
  • 同一用户每次获取token都保证只有最后一个token能够使用,之前的token都设为无效(单点token)。
    解决思路
    1.要重写RedisTokenStore中获取key的方式,即重写RedisTokenStore中的authenticationKeyGenerator对象的extractKey方法,使同一用户获取多次的Key值不同;
String key = authenticationKeyGenerator.extractKey(authentication);

借鉴一下网上一位老哥的改造代码

1.创建新类继承DefaultAuthenticationKeyGenerator ,重写extractKey方法

public class MyAuthenticationKeyGenerator extends DefaultAuthenticationKeyGenerator {
 2 
 3     private static final String CLIENT_ID = "client_id";
 4 
 5     private static final String SCOPE = "scope";
 6 
 7     private static final String USERNAME = "username";
 8 
 9     @Override
10     public String extractKey(OAuth2Authentication authentication) {
11         Map<String, String> values = new LinkedHashMap<String, String>();
12         OAuth2Request authorizationRequest = authentication.getOAuth2Request();
13         if (!authentication.isClientOnly()) {
14             //在用户名后面添加时间戳,使每次的key都不一样
15             values.put(USERNAME, authentication.getName()+System.currentTimeMillis());
16         }
17         values.put(CLIENT_ID, authorizationRequest.getClientId());
18         if (authorizationRequest.getScope() != null) {
19             values.put(SCOPE, OAuth2Utils.formatParameterList(new TreeSet<String>(authorizationRequest.getScope())));
20         }
21         return generateKey(values);
22     }
23 }

2.将新的Key生成器载入RedisTokenStore

@Configuration
public class MyTokenStoreConfig {
    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Bean
    public TokenStore tokenStore() {
        RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
        redisTokenStore.setAuthenticationKeyGenerator(new MyAuthenticationKeyGenerator());
        }
        return redisTokenStore;
    }
}

参考地址:https://blog.csdn.net/gangsijay888/article/details/81977796
https://www.cnblogs.com/cq-yangzhou/p/13207069.html

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

干饭两斤半

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值