How to Ensure Consistent OAuth2 Tokens Across Multiple Clients with Custom Key Generation
This article explains how to achieve consistent OAuth2 token handling across multiple clients and tenants by customizing the token key generation, overriding DefaultTokenServices logic, and configuring a RedisTokenStore with a custom AuthenticationKeyGenerator in a Spring Boot application.
Purpose
Explain how to keep token state consistent across different clients and tenants, ensuring that logout on one client invalidates tokens on all others.
Default DefaultTokenServices creation logic
<code>@Transactional
public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
// 1. Check if token exists
OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
OAuth2RefreshToken refreshToken = null;
if (existingAccessToken != null) {
if (existingAccessToken.isExpired()) {
if (existingAccessToken.getRefreshToken() != null) {
refreshToken = existingAccessToken.getRefreshToken();
tokenStore.removeRefreshToken(refreshToken);
}
tokenStore.removeAccessToken(existingAccessToken);
} else {
tokenStore.storeAccessToken(existingAccessToken, authentication);
return existingAccessToken;
}
}
// 2. Create new token
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;
}
</code>Determine if current user has a token
Shows the default logic of
RedisTokenStoreand the key generation.
<code>OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
@Override
public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {
// build default key
String key = authenticationKeyGenerator.extractKey(authentication);
// add prefix
byte[] serializedKey = serializeKey(AUTH_TO_ACCESS + key);
RedisConnection conn = getConnection();
try {
byte[] bytes = conn.get(serializedKey);
OAuth2AccessToken accessToken = deserializeAccessToken(bytes);
if (accessToken != null) {
OAuth2Authentication storedAuthentication = readAuthentication(accessToken.getValue());
if (storedAuthentication == null ||
!key.equals(authenticationKeyGenerator.extractKey(storedAuthentication))) {
storeAccessToken(accessToken, authentication);
}
}
return accessToken;
} finally {
conn.close();
}
}
</code>DefaultAuthenticationKeyGenerator key composition
<code>public String extractKey(OAuth2Authentication authentication) {
Map<String, String> values = new LinkedHashMap<>();
OAuth2Request authorizationRequest = authentication.getOAuth2Request();
if (!authentication.isClientOnly()) {
values.put(USERNAME, authentication.getName());
}
values.put(CLIENT_ID, authorizationRequest.getClientId());
if (authorizationRequest.getScope() != null) {
values.put(SCOPE, OAuth2Utils.formatParameterList(new TreeSet<>(authorizationRequest.getScope())));
}
return generateKey(values);
}
</code>Rewrite token key generation rules
<code>public class PigxAuthenticationKeyGenerator extends DefaultAuthenticationKeyGenerator {
private static final String SCOPE = "scope";
private static final String USERNAME = "username";
@Override
public String extractKey(OAuth2Authentication authentication) {
Map<String, String> values = new LinkedHashMap<>();
OAuth2Request authorizationRequest = authentication.getOAuth2Request();
if (!authentication.isClientOnly()) {
values.put(USERNAME, authentication.getName());
}
values.put(CLIENT_ID, authorizationRequest.getClientId());
if (authorizationRequest.getScope() != null) {
values.put(SCOPE, OAuth2Utils.formatParameterList(new TreeSet<>(authorizationRequest.getScope())));
}
// multi‑tenant handling can be added here
return generateKey(values);
}
}
</code>Inject customized TokenStore
<code>@Bean
public TokenStore tokenStore() {
RedisTokenStore tokenStore = new RedisTokenStore(redisConnectionFactory);
tokenStore.setPrefix(SecurityConstants.PIGX_PREFIX + SecurityConstants.OAUTH_PREFIX);
tokenStore.setAuthenticationKeyGenerator(new PigxAuthenticationKeyGenerator());
return tokenStore;
}
</code>Summary
Custom authentication key generator ensures token consistency across clients and tenants.
Configure RedisTokenStore with the custom generator and appropriate key prefix.
Refer to the author's blog for more OAuth2 extensions and a sample RBAC project.
Java Architecture Diary
Committed to sharing original, high‑quality technical articles; no fluff or promotional content.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.