专栏首页冷冷【小技巧】spring security oauth2 令牌实现多终端登录状态同步
原创

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

image

目的说明

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

我们来看 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;
	}

总结

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Spring Security OAuth2 实现登录互踢

    一个账号只能一处登录,类似的业务需求在现有后管类系统是非常常见的。 但在原有的 spring security oauth2 令牌方法流程(所谓的登录)无法满足...

    冷冷
  • Spring Boot 实现配置文件加解密原理

    接上文《失踪人口回归,mybatis-plus 3.3.2 发布》[1] ,提供了一个非常实用的功能 「数据安全保护」 功能,不仅支持数据源的配置加密,对于 s...

    冷冷
  • 【jfinal修仙系列】扩展CacheInterceptor支持Redis缓存

    jfinal内置CacheInterceptor 依赖于EhCachePlugin,是基于ehcache的。 CacheInterceptor 可以将 acti...

    冷冷
  • 玩转字符串篇--替换的鬼斧神工

    张风捷特烈
  • 线程安全的Map的小Demo

    Dream城堡
  • 【死磕 Spring】---- Spring 的环境&amp;属性:PropertySource、Environment、Profile

    spring.profiles.active 和 @Profile 这两个我相信各位都熟悉吧,主要功能是可以实现不同环境下(开发、测试、生产)参数配置的切换。其...

    用户1655470
  • Java做爬虫也很牛

    首先我们封装一个Http请求的工具类,用HttpURLConnection实现,当然你也可以用HttpClient, 或者直接用Jsoup来请求(下面会讲到Js...

    猿天地
  • 这样规范写代码,同事直呼“666”

    zhisheng
  • 这样规范写代码,同事直呼“666”

    Java团长
  • 这样规范写代码,同事直呼“666”

    乔戈里

扫码关注云+社区

领取腾讯云代金券