起因 & 环境#
环境:
- 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语句
这样登录就不会直接签发相同的令牌了。