oauth2中client_id_to_access数据膨胀问题

Posted 书梦一生

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了oauth2中client_id_to_access数据膨胀问题相关的知识,希望对你有一定的参考价值。

 


这是删除后,过了一两天就增长到了4万多条数据了。

 

查看了RedisTokenStore 发现token 会不断地往list塞值。

public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
byte[] serializedAccessToken = serialize(token);
byte[] serializedAuth = serialize(authentication);
byte[] accessKey = serializeKey(ACCESS + token.getValue());
byte[] authKey = serializeKey(AUTH + token.getValue());
byte[] authToAccessKey = serializeKey(AUTH_TO_ACCESS + authenticationKeyGenerator.extractKey(authentication));
byte[] approvalKey = serializeKey(UNAME_TO_ACCESS + getApprovalKey(authentication));
byte[] clientId = serializeKey(CLIENT_ID_TO_ACCESS + authentication.getOAuth2Request().getClientId());

RedisConnection conn = 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 ex) {
throw new RuntimeException(ex);
}
} else {
conn.set(accessKey, serializedAccessToken);
conn.set(authKey, serializedAuth);
conn.set(authToAccessKey, serializedAccessToken);
}
if (!authentication.isClientOnly()) {
conn.rPush(approvalKey, serializedAccessToken);
}
conn.rPush(clientId, serializedAccessToken);
if (token.getExpiration() != null) {
int seconds = token.getExpiresIn();
conn.expire(accessKey, seconds);
conn.expire(authKey, seconds);
conn.expire(authToAccessKey, seconds);
conn.expire(clientId, seconds);
conn.expire(approvalKey, seconds);
}
OAuth2RefreshToken refreshToken = token.getRefreshToken();
if (refreshToken != null && refreshToken.getValue() != null) {
byte[] refresh = serialize(token.getRefreshToken().getValue());
byte[] auth = serialize(token.getValue());
byte[] refreshToAccessKey = serializeKey(REFRESH_TO_ACCESS + token.getRefreshToken().getValue());
byte[] accessToRefreshKey = 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 ex) {
throw new RuntimeException(ex);
}
} else {
conn.set(refreshToAccessKey, auth);
conn.set(accessToRefreshKey, refresh);
}
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, seconds);
conn.expire(accessToRefreshKey, seconds);
}
}
}
conn.closePipeline();
} finally {
conn.close();
}
}
 

默认的实现方法 DefaultTokenServices,里面就写了如果失效了就会删除token,反之则不断塞值进去

public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {

OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
OAuth2RefreshToken refreshToken = null;
if (existingAccessToken != null) {
if (existingAccessToken.isExpired()) {
if (existingAccessToken.getRefreshToken() != null) {
refreshToken = existingAccessToken.getRefreshToken();
// The token store could remove the refresh token when the
// access token is removed, but we want to
// be sure...
tokenStore.removeRefreshToken(refreshToken);
}
tokenStore.removeAccessToken(existingAccessToken);
}
else {
// Re-store the access token in case the authentication has changed
tokenStore.storeAccessToken(existingAccessToken, authentication);
return existingAccessToken;
}
}

// Only create a new refresh token if there wasn\'t an existing one
// associated with an expired access token.
// Clients might be holding existing refresh tokens, so we re-use it in
// the case that the old access token
// expired.
if (refreshToken == null) {
refreshToken = createRefreshToken(authentication);
}
// But the refresh token itself might need to be re-issued if it has
// expired.
else if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken;
if (System.currentTimeMillis() > expiring.getExpiration().getTime()) {
refreshToken = createRefreshToken(authentication);
}
}

OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
tokenStore.storeAccessToken(accessToken, authentication);
// In case it was modified
refreshToken = accessToken.getRefreshToken();
if (refreshToken != null) {
tokenStore.storeRefreshToken(refreshToken, authentication);
}
return accessToken;

}

  

了解到在使用redis存储token时,client_id_to_access这个key存储了每个clientId申请的OAuth2AccessToken的集合,方便用来审计和应急处理跟clientId相关的token

查看RedisTokenStore代码,这是个set集合,每次生成token时会进行一次追加,导致数据急剧膨胀,redis空间不足,线上生产曾经遇到过最大达1.5G,且该key每次生成时会重置过期时间,导致永不删除。

解决方案:
1、直接删除该key,不影响token生成及checkToken;
2、魔改RedisTokenStore实现,调整数据结构存储;
3、过期时间设置不要过长,在登录最大间隔休息时间之内,这样不会因为重置过期时间一直导致数据不删除

 

以上是关于oauth2中client_id_to_access数据膨胀问题的主要内容,如果未能解决你的问题,请参考以下文章

Oauth2 身份验证失败

如何在我的 Spring Boot Security OAuth2 应用程序中仅为某些类启用 OAuth2?

仅在 JWT 令牌中而不是在 OAuth2 令牌中添加附加信息

标头中的 OAuth2.0 访问令牌

如何在 RestSharp 中使用 OAuth2

如何在@WebFluxTest 中禁用 oauth2 客户端注册?