转载

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

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

目的说明

解决不同客户端使用 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

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

我们来看 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 扩展方面欢迎翻我的博客 my.oschina.net/giegie
  • 配套实践项目欢迎关注 基于Spring Boot 2.1.7、 Spring Cloud Greenwich.SR2、 OAuth2 的RBAC 权限管理系统
    【小技巧】spring security oauth2 令牌实现多终端登录状态同步
原文  https://juejin.im/post/5d849998f265da03ec2e9e94
正文到此结束
Loading...