目的说明
解决不同客户端使用token,各个客户端的登录状态必须保持一致,退出状态实现一致。同上述问题类似如何解决不同租户相同用户名的人员的登录状态问题。
1.默认的DefaultTokenServices 创建逻辑
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
| @Transactional 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(); tokenStore.removeRefreshToken(refreshToken); } tokenStore.removeAccessToken(existingAccessToken); } else { tokenStore.storeAccessToken(existingAccessToken, authentication); return existingAccessToken; } }
OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken); tokenStore.storeAccessToken(accessToken, authentication); refreshToken = accessToken.getRefreshToken(); if (refreshToken != null) { tokenStore.storeRefreshToken(refreshToken, authentication); } return accessToken;
}
|
2.判断当前用户是否存在token

我们来看 RedisTokenStore 的默认逻辑,注意Token key 的生成逻辑
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
| OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
@Override public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) { String key = authenticationKeyGenerator.extractKey(authentication); byte[] serializedKey = serializeKey(AUTH_TO_ACCESS + key); byte[] bytes = null; RedisConnection conn = getConnection(); try { bytes = conn.get(serializedKey); } finally { conn.close(); } 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; }
|
3. DefaultAuthenticationKeyGenerator
- 主要参考一下 当前用户的 username clientId scope ,这样导致不同客户端的token 不一致,某个客户端退出不会影响其他客户端
1 2 3 4 5 6 7 8 9 10 11 12
| public String extractKey(OAuth2Authentication authentication) { Map<String, String> values = new LinkedHashMap<String, String>(); 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<String>(authorizationRequest.getScope()))); } return generateKey(values); }
|
重写token key 的生成规则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class AiopsAuthenticationKeyGenerator 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<String, String>(); OAuth2Request authorizationRequest = authentication.getOAuth2Request(); if (!authentication.isClientOnly()) { values.put(USERNAME, authentication.getName()); } if (authorizationRequest.getScope() != null) { values.put(SCOPE, OAuth2Utils.formatParameterList(new TreeSet<String>(authorizationRequest.getScope()))); }
return generateKey(values); } }
|
注入tokenstroe 即可实现如上效果
1 2 3 4 5 6 7
| @Bean public TokenStore tokenStore() { RedisTokenStore tokenStore = new RedisTokenStore(redisConnectionFactory); tokenStore.setPrefix(SecurityConstants.AIOPS_PREFIX + SecurityConstants.OAUTH_PREFIX); tokenStore.setAuthenticationKeyGenerator(new AiopsAuthenticationKeyGenerator()); return tokenStore; }
|