前言:
最近开发认证服务的时候,发现使用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