【小技巧】spring security oauth2 令牌實現多終端登入狀態同步
阿新 • • 發佈:2019-12-31
目的說明
解決不同客戶端使用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>();
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 許可權管理系統