Spring Security OAuth2 Redis存储token refresh_token永不过期问题解决

OAuth2AccessToken接口的默认实现是DefaultOAuth2AccessToken类(自带过期时间属性)
OAuth2RefreshToken接口的默认实现是DefaultOAuth2RefreshToken类(不带过期时间属性)
ExpiringOAuth2RefreshToken接口父接口是OAuth2RefreshTokenExpiringOAuth2RefreshToken的默认实现是DefaultExpiringOAuth2RefreshToken(自带过期时间属性)

自定义方式生成access_token和refresh_token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* @Description: 用户自定义token令牌,包括access_token和refresh_token
* @Package: cn.appblog.security.oauth2.enhancer.UserTokenEnhancer
* @Version: 1.0
*/
public class UserTokenEnhancer implements TokenEnhancer {
/**
* 重新定义令牌token
*/
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
if(accessToken instanceof DefaultOAuth2AccessToken){
DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) accessToken;
token.setValue(getToken());
OAuth2RefreshToken refreshToken = token.getRefreshToken();
if(refreshToken instanceof OAuth2RefreshToken){
token.setRefreshToken(new OAuth2RefreshToken(getToken()));
}
Map<String, Object> additionalInformation = Maps.newHashMap();
additionalInformation.put("client_id", authentication.getOAuth2Request().getClientId());
//添加额外配置信息
token.setAdditionalInformation(additionalInformation);
return token;
}
return accessToken;
}

/**
* 生成自定义token
*/
private String getToken() {
return StringUtils.join(UUID.randomUUID().toString().replace("-", ""));
}
}

在实际的测试环境中我可以拿到access_token的过期时间,并且在redis的客户端查看access_token相关键值对都是跟我设置的过期时间是一致的,但是refresh_token设置的过期
时间一直不起作用,TTL显示-1,也就是一直有效,感觉就很奇怪,所以翻看TokenStore的实现类RedisTokenStore

生成access_token键值对的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
//序列化相关key
byte[] serializedAccessToken = this.serialize((Object)token);
byte[] serializedAuth = this.serialize((Object)authentication);
byte[] accessKey = this.serializeKey("access:" + token.getValue());
byte[] authKey = this.serializeKey("auth:" + token.getValue());
byte[] authToAccessKey = this.serializeKey("auth_to_access:" + this.authenticationKeyGenerator.extractKey(authentication));
byte[] approvalKey = this.serializeKey("uname_to_access:" + getApprovalKey(authentication));
byte[] clientId = this.serializeKey("client_id_to_access:" + authentication.getOAuth2Request().getClientId());
RedisConnection conn = this.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 var24) {
throw new RuntimeException(var24);
}
} else {
conn.set(accessKey, serializedAccessToken);
conn.set(authKey, serializedAuth);
conn.set(authToAccessKey, serializedAccessToken);
}

if (!authentication.isClientOnly()) {
conn.sAdd(approvalKey, new byte[][]{serializedAccessToken});
}

conn.sAdd(clientId, new byte[][]{serializedAccessToken});
//设置access_token过期时间
if (token.getExpiration() != null) {
int seconds = token.getExpiresIn();
conn.expire(accessKey, (long)seconds);
conn.expire(authKey, (long)seconds);
conn.expire(authToAccessKey, (long)seconds);
conn.expire(clientId, (long)seconds);
conn.expire(approvalKey, (long)seconds);
}

OAuth2RefreshToken refreshToken = token.getRefreshToken();
if (refreshToken != null && refreshToken.getValue() != null) {
byte[] refresh = this.serialize(token.getRefreshToken().getValue());
byte[] auth = this.serialize(token.getValue());
byte[] refreshToAccessKey = this.serializeKey("refresh_to_access:" + token.getRefreshToken().getValue());
byte[] accessToRefreshKey = this.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 var23) {
throw new RuntimeException(var23);
}
} else {
conn.set(refreshToAccessKey, auth);
conn.set(accessToRefreshKey, refresh);
}
//判断refresh_token对象是否是ExpiringOAuth2RefreshToken的实例对象,这一段是设置refresh_token的关键,如果是就会进行过期时间
//设置,否则生成的refresh_token相关的键值对永远有效
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, (long)seconds);
conn.expire(accessToRefreshKey, (long)seconds);
}
}
}

conn.closePipeline();
} finally {
conn.close();
}
}

上面的refreshToken instanceof ExpiringOAuth2RefreshToken这段代码是来判断刷新token是否是带有有效期时间的ExpiringOAuth2RefreshToken实例对象,我们可以
看到上面自定义的生成refresh_token的实例对象是OAuth2RefreshToken类型,只带有一个refresh_token值,而没有有效时间的字段值,我们看下ExpiringOAuth2RefreshToken类的源码:

1
2
3
4
5
6
7
package org.springframework.security.oauth2.common;

import java.util.Date;

public interface ExpiringOAuth2RefreshToken extends OAuth2RefreshToken {
Date getExpiration();
}

可以看到ExpiringOAuth2RefreshToken是OAuth2RefreshToken的子类,所以我们可以将生成的refresh_token实例对象更改为ExpiringOAuth2RefreshToken对象,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 重新定义令牌token
*/
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
if (accessToken instanceof DefaultOAuth2AccessToken) {
DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) accessToken;
token.setValue(getToken());
//使用DefaultExpiringOAuth2RefreshToken类生成refresh_token,自带过期时间,否则不生效,refresh_token一直有效
DefaultExpiringOAuth2RefreshToken refreshToken = (DefaultExpiringOAuth2RefreshToken) token.getRefreshToken();
//OAuth2RefreshToken refreshToken = token.getRefreshToken();
if (refreshToken instanceof DefaultExpiringOAuth2RefreshToken) {
token.setRefreshToken(new DefaultExpiringOAuth2RefreshToken(getToken(), refreshToken.getExpiration()));
}
Map<String, Object> additionalInformation = Maps.newHashMap();
additionalInformation.put("client_id", authentication.getOAuth2Request().getClientId());
//添加额外配置信息
token.setAdditionalInformation(additionalInformation);
return token;
}
return accessToken;
}

经测试上面的方案完美解决自定义token生成refresh_token永不过期问题

本文转载参考 原文 并加以调试

Powered by AppBlog.CN     浙ICP备14037229号

Copyright © 2012 - 2020 APP开发技术博客 All Rights Reserved.

访客数 : | 访问量 :