起因 & 環境#
環境:
- 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();
// トークンストアは、アクセストークンが削除されるときに
// リフレッシュトークンを削除することができますが、私たちは
// 確実にしたいのです...
tokenStore.removeRefreshToken(refreshToken);
}
tokenStore.removeAccessToken(existingAccessToken);
}
else {
// 認証が変更された場合に備えてアクセストークンを再保存する
tokenStore.storeAccessToken(existingAccessToken, authentication);
return existingAccessToken;
}
}
// 既存のアクセストークンに関連付けられたリフレッシュトークンがない場合のみ
// 新しいリフレッシュトークンを作成します。
// クライアントは既存のリフレッシュトークンを保持している可能性があるため、
// 古いアクセストークンが
// 期限切れになった場合に再利用します。
if (refreshToken == null) {
refreshToken = createRefreshToken(authentication);
}
// ただし、リフレッシュトークン自体は
// 期限切れの場合は再発行する必要があります。
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);
// 変更された場合に備えて
refreshToken = accessToken.getRefreshToken();
if (refreshToken != null) {
tokenStore.storeRefreshToken(refreshToken, authentication);
}
return accessToken;
}
その中に// 認証が変更された場合に備えてアクセストークンを再保存する
という行が含まれており、これは以前に発行された未期限切れのトークンを再保存することを意味します。新しいトークンを発行するかのように扱われます。よし、こう書きましょう。
次に、このコードを改訂して、無効にし、新しいトークンを発行する必要があります。ここを変更するだけです:
@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();
// トークンストアは、アクセストークンが削除されるときに
// リフレッシュトークンを削除することができますが、私たちは
// 確実にしたいのです...
tokenStore.removeRefreshToken(refreshToken);
}
tokenStore.removeAccessToken(existingAccessToken);
}
else {
// ここを変更します
// 認証が変更された場合に備えてアクセストークンを再保存する
tokenStore.storeAccessToken(existingAccessToken, authentication);
return existingAccessToken;
}
}
// 他のコードは変更せず、そのままにします
}
これを次のコードに変更します:
tokenStore.removeAccessToken(existingAccessToken);
// return文を削除します
これにより、ログイン時に同じトークンが直接発行されなくなります。