起因 & 環境#
環境:
- Java 8
- Spring Security Oauth 2.0.20.BUILD-SNAPSHOT 點擊這裡前往原始代碼倉庫
在工作時,發現了一個問題,為什麼相同的帳號在不同的地方會用相同的令牌?這在正常的情況下不是合理的。如果有新的登錄,我們也許會把原有令牌清除,以免別人拿去利用。
追查#
首先,打開了 OAuth2 授權伺服器的代碼,發現並沒有什麼太多令牌生成相關的邏輯,只有AuthorizationServerConfiguration
裡面有這段代碼:
@Bean
@Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setClientDetailsService(clientDetailsService);
defaultTokenServices.setAccessTokenValiditySeconds(7200);
defaultTokenServices.setSupportRefreshToken(false);
return defaultTokenServices;
}
嗯?令牌服務,關於令牌的只有這部分,所以我們需要繼續向下追查,直接去看DefaultTokenServices
類的內容,然後就發現了這段代碼:
@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();
// The token store could remove the refresh token when the
// access token is removed, but we want to
// be sure...
tokenStore.removeRefreshToken(refreshToken);
}
tokenStore.removeAccessToken(existingAccessToken);
}
else {
// Re-store the access token in case the authentication has changed
tokenStore.storeAccessToken(existingAccessToken, authentication);
return existingAccessToken;
}
}
// Only create a new refresh token if there wasn't an existing one
// associated with an expired access token.
// Clients might be holding existing refresh tokens, so we re-use it in
// the case that the old access token
// expired.
if (refreshToken == null) {
refreshToken = createRefreshToken(authentication);
}
// But the refresh token itself might need to be re-issued if it has
// expired.
else if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken;
if (System.currentTimeMillis() > expiring.getExpiration().getTime()) {
refreshToken = createRefreshToken(authentication);
}
}
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;
}
其中包含了一句// Re-store the access token in case the authentication has changed
,這翻譯過來不就是把之前簽發的沒有過期的令牌重新存了一次,就當作簽發新的令牌了,好好好,這麼寫。
然後我們就需要改寫一下這段代碼,應該使其失效,然後頒發新的令牌;只需要將這裡:
@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();
// The token store could remove the refresh token when the
// access token is removed, but we want to
// be sure...
tokenStore.removeRefreshToken(refreshToken);
}
tokenStore.removeAccessToken(existingAccessToken);
}
else {
// 將這裡的幾行進行修改
// Re-store the access token in case the authentication has changed
tokenStore.storeAccessToken(existingAccessToken, authentication);
return existingAccessToken;
}
}
// 其他代碼,保持不變,除非有其他修改
}
改為如下代碼:
tokenStore.removeAccessToken(existingAccessToken);
// 並刪除return語句
這樣登錄就不會直接簽發相同的令牌了。