Spring security oauth重复签发令牌的坑

起因 & 环境

环境：

Java 8

Spring Security Oauth 2.0.20.BUILD-SNAPSHOT 点击这里前往原始代码仓库

在工作的时候，发现了个问题，为什么相同的账号在不同的地方会用相同的令牌？这在正常的情况下不是合理的。如果有新的登录，我们也许把原有令牌清除，以免别人拿去利用。

追查

首先，打开了 OAuth2 授权服务器的代码，发现并没有什么太多令牌生成相关的逻辑，只有AuthorizationServerConfiguration里面有这段代码：

Copy
@Bean
@Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setClientDetailsService(clientDetailsService);
defaultTokenServices.setAccessTokenValiditySeconds(7200);
defaultTokenServices.setSupportRefreshToken(false);
return defaultTokenServices;
}

嗯？令牌服务，关于令牌的只有这部分，所以我们需要继续向下追查，直接去看DefaultTokenServices类的内容，然后就发现了这段代码：

Copy
@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，这翻译过来不就是把之前签发的没有过期的令牌重新存了一次，就当作签发新的令牌了，好好好，这么写。

然后我们就需要改写一下这段代码，应该使其失效，然后颁发新的令牌；只需要将这里：

Copy
@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;
}
}
// 其他代码，保持不变，除非有其他修改
}

改为如下代码：

Copy
tokenStore.removeAccessToken(existingAccessToken);
// 并删除return语句

这样登录就不会直接签发相同的令牌了。