読者です 読者をやめる 読者になる 読者になる

GeekFactory

int128.hatenablog.com

Spring Security OAuth2のリトライを@Retryableで書く

Spring Security OAuth2のアクセストークン取得で接続失敗に対してリトライを行いたい場合、Spring Retryを使うと簡単に実現できます。

やりたいこと

  • Spring BootのアプリでOAuth 2.0クライアントを利用する。
  • Spring Security OAuth2でアクセストークンを取得する。( OAuth2RestTemplateFeignRequestInterceptor は内部でSpring Security OAuth2を利用している)
  • OAuthクライアントの接続失敗でリトライしたい。

実現方法

Spring Security OAuth2の AccessTokenProvider にはインターセプタを設定するメソッドが用意されており、アクセストークン取得のリクエストを投げる前後で任意の処理を入れることが可能です。 インターセプタのメソッドにSpring Retryの @Retryable アノテーションを付けることで、簡単にリトライを実現できます。

@EnableRetry
@Configuration
class OAuth2Configuration {
  @Autowired
  RetryableAccessTokenRequestInterceptor accessTokenRequestInterceptor

  @Bean
  AccessTokenProvider accessTokenProvider() {
    def accessTokenProviders = [
      new AuthorizationCodeAccessTokenProvider(),
      new ImplicitAccessTokenProvider(),
      new ResourceOwnerPasswordAccessTokenProvider(),
      new ClientCredentialsAccessTokenProvider()
    ]
    // インターセプタを設定する
    accessTokenProviders*.interceptors = [accessTokenRequestInterceptor()]
    new AccessTokenProviderChain(accessTokenProviders)
  }
}
@Component
class RetryableAccessTokenRequestInterceptor implements ClientHttpRequestInterceptor {
  // 接続失敗に対するリトライを宣言する
  @Retryable(value = {ConnectException.class}, backoff = @Backoff(delay = 500))
  @Override
  ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
    execution.execute(request, body)
  }
}

あらかじめ、依存関係にSpring Retryを追加しておく必要があります。

実行結果

リトライの様子を確認するため、ログレベルを設定しておきます。

logging.level:
  # リトライのログ
  org.springframework.retry: DEBUG
  # Spring Security OAuth2の通信ログ
  org.apache.http.wire: DEBUG

アクセストークン取得を実行すると、以下のようなログが出力されます。

o.s.retry.support.RetryTemplate          : Retry: count=0
org.apache.http.wire                     : http-outgoing-1 << "[read] I/O error: Connection reset"
o.s.retry.support.RetryTemplate          : Checking for rethrow: count=1
o.s.retry.support.RetryTemplate          : Retry: count=1
o.s.retry.support.RetryTemplate          : Checking for rethrow: count=2
o.s.retry.support.RetryTemplate          : Retry: count=2
o.s.retry.support.RetryTemplate          : Checking for rethrow: count=3
o.s.retry.support.RetryTemplate          : Retry failed last attempt: count=3

まとめ

インターセプタはアクセストークン取得のリクエストを微修正する*1ことが本来の用途ですが、リトライの用途にも使えます。

*1:RFCに準拠していないサービスに対応させるためのハックとか