You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何按用户名查询用户在所有客户端的全部OAuth2令牌?

How to Retrieve All Tokens for a User Across All Clients with RedisTokenStore

Great question! You’re absolutely right that the default TokenStore interface only provides findTokensByClientIdAndUserName, which forces you to iterate through all known clients—an inefficient approach that can also miss dynamically registered clients. Since you’re using RedisTokenStore, we can leverage its built-in Redis key structure to implement a far more efficient solution. Here are the top approaches:

Approach 1: Extend RedisTokenStore to Use Existing Username Mappings

RedisTokenStore already maintains a mapping between usernames and authentication IDs under the key pattern uname_to_access:{username}. This key stores a Redis Set of all authentication IDs linked to the user. We can extend the store to use this directly:

First, create a custom token store that inherits from RedisTokenStore and adds the missing method:

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.token.RedisTokenStore;

import java.util.Collection;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

public class CustomRedisTokenStore extends RedisTokenStore {

    private static final String USERNAME_TO_ACCESS_KEY_PREFIX = "uname_to_access:";

    public CustomRedisTokenStore(RedisTemplate<String, Object> redisTemplate) {
        super(redisTemplate);
    }

    // New method to fetch all tokens for a user across all clients
    public Collection<OAuth2AccessToken> findTokensByUserName(String username) {
        String userKey = USERNAME_TO_ACCESS_KEY_PREFIX + username;
        Set<String> authIds = getRedisTemplate().opsForSet().members(userKey);

        if (authIds == null || authIds.isEmpty()) {
            return null;
        }

        // Convert authentication IDs to actual access tokens
        return authIds.stream()
                .map(this::getAccessTokenFromAuthId)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
    }

    private OAuth2AccessToken getAccessTokenFromAuthId(String authId) {
        String authKey = "auth:" + authId;
        Object authentication = getRedisTemplate().opsForValue().get(authKey);
        
        if (authentication == null) {
            return null;
        }

        // Retrieve token ID from the authentication object, then fetch the token
        String tokenId = getTokenId(authentication);
        return readAccessToken(tokenId);
    }
}

Then, replace your default RedisTokenStore with this custom implementation in your authorization server config:

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Bean
    public TokenStore tokenStore() {
        return new CustomRedisTokenStore(redisTemplate);
    }

    // Rest of your authorization server configuration...
}

This approach is efficient because it uses the existing Redis structure—no extra storage overhead, and Redis Set operations are fast. It also avoids the pitfalls of client list iteration.

Approach 2: Use Redis SCAN for Large Token Collections

If a user has a huge number of tokens, using members() might block Redis temporarily. For this scenario, switch to the SCAN command to fetch authentication IDs in batches:

Update the findTokensByUserName method like this:

import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import java.util.HashSet;

public Collection<OAuth2AccessToken> findTokensByUserName(String username) {
    String userKey = USERNAME_TO_ACCESS_KEY_PREFIX + username;
    Set<String> authIds = new HashSet<>();

    // Use SCAN to avoid blocking Redis with large datasets
    Cursor<String> cursor = getRedisTemplate().opsForSet().scan(userKey, ScanOptions.scanOptions().match("*").build());
    while (cursor.hasNext()) {
        authIds.add(cursor.next());
    }
    cursor.close();

    if (authIds.isEmpty()) {
        return null;
    }

    return authIds.stream()
            .map(this::getAccessTokenFromAuthId)
            .filter(Objects::nonNull)
            .collect(Collectors.toList());
}

This is a safer choice for high-traffic systems where large token sets are possible.

Approach 3: Custom Token Association Structure (Optional)

If you need more control over the storage structure, you can manually maintain a custom Redis key (e.g., user_all_tokens:{username}) that stores all token IDs for a user. However, this requires overriding storeAccessToken and removeAccessToken to keep the custom set in sync with token lifecycle events. This is only necessary if you have unique storage requirements—Approach 1 is sufficient for most cases.

Key Notes

  • Ensure your RedisTemplate uses the same serialization strategy as RedisTokenStore (default is JdkSerializationRedisSerializer) to avoid deserialization errors.
  • RedisTokenStore automatically handles expired tokens by setting TTLs on Redis keys, so you don’t need to manually clean up stale entries.

内容的提问来源于stack exchange,提问作者schatten

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 10:31:25