多终端同时登录_【小技巧】spring security oauth2 令牌实现多终端登录状态同步

4f607cb68cf9484fee01bdc302dd832b.png

目的说明

解决不同客户端使用 token,各个客户端的登录状态必须保持一致,退出状态实现一致。同上述问题类似如何解决不同租户相同用户名的人员的登录状态问题。

默认的DefaultTokenServices 创建逻辑

@Transactional

public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {

// 1. 判断是否存在Token

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. 创建新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;

}

判断当前用户是否存在token

97f30aa54e699d59cd0a6a3a0eb8c49e.png

我们来看 RedisTokenStore 的默认逻辑,注意Token key 的生成逻辑

OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);

@Override

public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {

// 构造 默认保存的

String key = authenticationKeyGenerator.extractKey(authentication);

// key 加上前缀

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;

}

DefaultAuthenticationKeyGenerator拼接key

  • 主要参考一下 当前用户的 username clientId scope ,这样导致不同客户端的token 不一致,某个客户端退出不会影响其他客户端

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 的生成规则

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<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())));

}

// 如果是多租户系统,这里要区分租户ID 条件

return generateKey(values);

}

}

注入tokenstroe 即可实现如上效果

@Bean

public TokenStore tokenStore() {

RedisTokenStore tokenStore = new RedisTokenStore(redisConnectionFactory);

tokenStore.setPrefix(SecurityConstants.PIGX_PREFIX + SecurityConstants.OAUTH_PREFIX);

tokenStore.setAuthenticationKeyGenerator(new PigxAuthenticationKeyGenerator());

return tokenStore;

}

总结

  • 更多关于oauth2 扩展方面欢迎翻我的博客https://my.oschina.net/giegie

  • 配套实践项目欢迎关注 基于Spring Boot 2.1.7、 Spring Cloud Greenwich.SR2、 OAuth2 的RBAC 权限管理系统19ba1b5db92edb6cbc239f6a0bc7dbe8.png


版权声明:本文为weixin_39657125原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。