如何按用户名查询用户在所有客户端的全部OAuth2令牌?
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
RedisTemplateuses the same serialization strategy asRedisTokenStore(default is JdkSerializationRedisSerializer) to avoid deserialization errors. RedisTokenStoreautomatically handles expired tokens by setting TTLs on Redis keys, so you don’t need to manually clean up stale entries.
内容的提问来源于stack exchange,提问作者schatten

