GeekFactory

int128.hatenablog.com

Spring Security OAuth2でリクエストログを出力する

Spring Security OAuth2でアクセストークンのリクエストとレスポンスのログを出力するには、Apache HttpClientを使うと簡単です。

概要

  • Spring Security OAuth2のアクセストークン取得はRestTemplateを利用しています。
  • RestTemplateはデフォルトではHttpURLConnectionを利用しますが、HttpClientを利用することもできます。
  • HttpClientはログレベルを設定するだけでヘッダやボディのログを出力してくれます。

OAuth2RestTemplateの場合

OAuth2RestTemplateを生成する際に以下を行うことで、アクセストークンのリクエストやレスポンスのログ出力が有効になります。アクセストークン取得後のリクエストやレスポンスは別物なので注意してください。

@Configuration
class AppConfiguration {
    @Bean
    OAuth2RestTemplate oAuth2RestTemplate(OAuth2ClientContext oAuth2ClientContext) {
        def restTemplate = new OAuth2RestTemplate(resource(), oAuth2ClientContext)

        // HttpClientを利用するように設定する
        def requestFactory = new HttpComponentsClientHttpRequestFactory()
        def accessTokenProviders = Arrays.asList(
            new AuthorizationCodeAccessTokenProvider(),
            new ImplicitAccessTokenProvider(),
            new ResourceOwnerPasswordAccessTokenProvider(),
            new ClientCredentialsAccessTokenProvider()
        )
        accessTokenProviders*.requestFactory = requestFactory
        restTemplate.accessTokenProvider = new AccessTokenProviderChain(accessTokenProviders)

        restTemplate
    }

    private resource() {
        def details = new ClientCredentialsResourceDetails()
        // TODO: client_id, client_secretなどを設定する
        details
    }
}
@RestController
class HelloController {
    @Inject
    OAuth2RestTemplate restTemplate

    @GetMapping('/hello')
    Hello helloWorld() {
        // OAuth2RestTemplateを利用してAPIを実行する
        def response = restTemplate.getForEntity("https://.../hello", Hello)
    }
}

OAuth2FeignRequestInterceptorの場合

OAuth2FeignRequestInterceptorはAccessTokenProviderを設定するメソッドを提供していないため、クラスを継承して拡張する必要があります。

class HelloOAuth2FeignRequestInterceptor extends OAuth2FeignRequestInterceptor {
    private final OAuth2ClientContext oAuth2ClientContext

    private final AccessTokenProvider accessTokenProvider = {
        // HttpClientを利用するように設定する
        def requestFactory = new HttpComponentsClientHttpRequestFactory()
        def accessTokenProviders = Arrays.asList(
            new AuthorizationCodeAccessTokenProvider(),
            new ImplicitAccessTokenProvider(),
            new ResourceOwnerPasswordAccessTokenProvider(),
            new ClientCredentialsAccessTokenProvider()
        )
        accessTokenProviders*.requestFactory = requestFactory
        new AccessTokenProviderChain(accessTokenProviders)
    }()

    private final OAuth2ProtectedResourceDetails resource = {
        def details = new ClientCredentialsResourceDetails()
        // TODO: client_id, client_secretなどを設定する
        details
    }()

    def HelloOAuth2FeignRequestInterceptor(OAuth2ClientContext oAuth2ClientContext) {
        super(oAuth2ClientContext, null)
        this.oAuth2ClientContext = oAuth2ClientContext
    }

    /**
     * @see OAuth2FeignRequestInterceptor#acquireAccessToken()
     */
    @Override
    protected OAuth2AccessToken acquireAccessToken() {
        // (中略) OAuth2FeignRequestInterceptor#acquireAccessToken() の実装をコピー
    }
}

OAuth2FeignRequestInterceptorに setAccessTokenProvider() メソッドを追加するPull Requestがあるので、将来のバージョンでは改善されるかもしれません。

github.com

ログレベルの設定

HttpClientがログを出力するようにapplication.ymlを設定します。

logging.level:
  # ヘッダのみ出力する場合
  org.apache.http.headers: DEBUG
  # ヘッダとボディを出力する場合
  org.apache.http.wire: DEBUG

アクセストークンリクエストのカスタマイズ(おまけ)

上記と同様に setTokenRequestEnhancer() を利用することで、アクセストークンのリクエストをカスタマイズできます。例えば、client_idclient_secret をボディに入れるといった独自仕様に対応できます。

    @Bean
    OAuth2RestTemplate oAuth2RestTemplate(OAuth2ClientContext oAuth2ClientContext) {
        def restTemplate = new OAuth2RestTemplate(resource(), oAuth2ClientContext)
        def accessTokenProvider = new ClientCredentialsAccessTokenProvider()
        // HttpClientを利用するように設定する
        accessTokenProvider.requestFactory = new HttpComponentsClientHttpRequestFactory()
        // アクセストークンリクエストをカスタマイズする
        accessTokenProviders*.tokenRequestEnhancer = new RequestEnhancer() {
            @Override
            void enhance(AccessTokenRequest request, OAuth2ProtectedResourceDetails resource, MultiValueMap<String, String> form, HttpHeaders headers) {
            }
        }
        restTemplate.accessTokenProvider = accessTokenProvider
        restTemplate
    }

ご参考まで。

doma-spring-bootのSQL例外変換

doma-spring-bootを利用すると、Doma2の例外クラス(JdbcException)をSpring Transactionの例外クラス(DataAccessException)に変換してくれます。例外変換の仕様が明文化されていないようなので調べてみました。どこかにまとめてあったら教えてください。

前提

  • doma-spring-boot-1.1

例外変換の仕様

契機 Doma2の例外クラス → Spring Transactionの例外クラス
楽観的排他制御エラー OptimisticLockExceptionOptimisticLockingFailureException
一意制約違反 UniqueConstraintExceptionDuplicateKeyException
1件であることを期待する検索系SQLの結果が2件以上である場合 NonUniqueResultExceptionIncorrectResultSizeDataAccessException
1列であることを期待する検索系SQLの結果が1列でない場合 NonSingleColumnExceptionIncorrectResultSizeDataAccessException
1件以上存在することを期待する検索系SQLの結果が0件である場合 NoResultExceptionEmptyResultDataAccessException
結果セットに未知のカラムが存在する場合 UnknownColumnExceptionTypeMismatchDataAccessException
結果セットのカラムにマッピングされないプロパティが存在する場合 ResultMappingExceptionTypeMismatchDataAccessException
その他のSQL例外 JdbcExceptionSQLExceptionTranslator により変換される
その他の例外 JdbcExceptionUncategorizedDataAccessException

なお、例外変換はapplication.ymlで以下を設定すると無効化できます。

# Whether convert JdbcException into DataAccessException.
doma.exception-translation-enabled: false

共通例外処理を実装する場合などに役に立てば幸いです。

参考資料

2016年の振り返り

2016年もお世話になりました。

概況

f:id:int128:20161231234424p:plain

振り返り

  • 仕事
    • Spring CloudとかSwaggerとかAWSとか触ってた。
    • JIRA, Confluence, Mattermost, ownCloud, GitBucket, Jenkins, Artifactory, SonarQubeとかをDocker Composeベースで開発基盤に導入してた。
    • 上期は暇で、下期は炎上してた。
  • 開発
  • プライベート
    • 3ヶ月弱の育休を取得した。視野が広がった。
    • 家族と過ごす時間を大切にできた。(12月以外)
    • 環境の変化に対応しながら家計を管理できた。

計画

エンジニア35歳定年を迎えます。家族と過ごす時間を大切にしつつ、専門性の高い仕事をしていけるように、働く環境を改善していきたいと思います。

2017年もよろしくお願いいたします。