OAuth2RestTemplateのSpring Security 5の置き換え


14

spring-security-oauth2:2.4.0.RELEASEようなクラスOAuth2RestTemplateOAuth2ProtectedResourceDetailsおよびClientCredentialsAccessTokenProvider非推奨のように、すべてのマークされています。

これらのクラスのjavadocから、人々が中心的なSpring-Security 5プロジェクトに移行する必要があることをほのめかすSpring Security移行ガイドが示されています。ただし、このプロジェクトでユースケースを実装する方法を見つけるのに苦労しています。

すべてのドキュメントと例では、アプリケーションへの受信リクエストを認証し、サードパーティのOAuthプロバイダーを使用してIDを確認する場合の、サードパーティのOAuthプロバイダーとの統合について説明しています。

私のユースケースRestTemplateでは、OAuthで保護されている外部サービスにをリクエストするだけです。現在、私はOAuth2ProtectedResourceDetailsに渡すクライアントIDとシークレットを使用してを作成していますOAuth2RestTemplate。また、使用しているOAuthプロバイダーが必要とする追加のヘッダーをトークンリクエストにClientCredentialsAccessTokenProvider追加するカスタムをに追加しましたOAuth2ResTemplate

Spring-Security 5のドキュメントで、トークンリクエストのカスタマイズに言及しているセクションを見つけましたが、これも、サードパーティのOAuthプロバイダーによる着信リクエストの認証に関連しているようです。これをのようなものとどのように組み合わせて使用ClientHttpRequestInterceptorして、外部サービスへの各送信リクエストが最初にトークンを取得し、次にそれをリクエストに追加することを保証するかは明確ではありません。

また、上記の移行ガイドにはOAuth2AuthorizedClientService、インターセプターでの使用に役立つと記載されているへの参照がありますが、これは、を使用しClientRegistrationRepositoryたい場合、サードパーティプロバイダーの登録を維持しているように見えるに依存しているようです着信リクエストが認証されることを保証するために提供します。

アプリケーションからの発信要求に追加するトークンを取得するためにOAuthプロバイダーを登録するために、spring-security 5の新機能を利用できる方法はありますか?

回答:


15

Spring Security 5.2.xのOAuth 2.0クライアント機能はサポートしていませんが、サポートRestTemplateしているだけWebClientです。Spring Security Referenceを参照してください。

HTTPクライアントのサポート

  • WebClient サーブレット環境の統合(保護されたリソースを要求するため)

またRestTemplate、将来のバージョンでは廃止される予定です。RestTemplate javadocを参照してください。

注: 5.0以降、非ブロッキング、リアクティブ org.springframework.web.reactive.client.WebClientは、RestTemplateストリーミングシナリオだけでなく、同期と非同期の両方を効率的にサポートするの最新の代替手段を提供します。RestTemplate将来のバージョンでは廃止され、今後追加された主要な新機能を持っていません。WebClient詳細とコード例については、Spring Frameworkリファレンスドキュメントのセクションをご覧ください。

したがって、最善の解決策はを放棄RestTemplateすることですWebClient


WebClientクライアント資格情報フローの使用

プログラムで、またはSpring Boot自動構成を使用して、クライアント登録とプロバイダーを構成します。

spring:
  security:
    oauth2:
      client:
        registration:
          custom:
            client-id: clientId
            client-secret: clientSecret
            authorization-grant-type: client_credentials
        provider:
          custom:
            token-uri: http://localhost:8081/oauth/token

…とOAuth2AuthorizedClientManager @Bean

@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
        ClientRegistrationRepository clientRegistrationRepository,
        OAuth2AuthorizedClientRepository authorizedClientRepository) {

    OAuth2AuthorizedClientProvider authorizedClientProvider =
            OAuth2AuthorizedClientProviderBuilder.builder()
                    .clientCredentials()
                    .build();

    DefaultOAuth2AuthorizedClientManager authorizedClientManager =
            new DefaultOAuth2AuthorizedClientManager(
                    clientRegistrationRepository, authorizedClientRepository);
    authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

    return authorizedClientManager;
}

提供さWebClientれたで使用するようにインスタンスを構成します。ServerOAuth2AuthorizedClientExchangeFilterFunctionOAuth2AuthorizedClientManager

@Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
    ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
            new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
    oauth2Client.setDefaultClientRegistrationId("custom");
    return WebClient.builder()
            .apply(oauth2Client.oauth2Configuration())
            .build();
}

このWebClientインスタンスを使用してリクエストを行おうとすると、最初に認証サーバーにトークンをリクエストし、リクエストに含めます。


おかげで、いくつかの問題は解決しましたが、上記のリンクされたすべてのドキュメントから、インターセプター(または新しい用語が何であれWebClient)または同様のものがOAuthトークンをフェッチするために使用される例を見つけるのに苦労しています送信リクエストに追加するためのカスタムOAuthプロバイダー(Facebook / GoogleのようなOoTBをサポートするプロバイダーではありません)。すべての例は、他のプロバイダーによる着信要求の認証に焦点を当てているようです。良い例の指針はありますか?
マットウィリアムス

1
@MattWilliams WebClientクライアント資格情報付与タイプでを使用する方法の例を使用して回答を更新しました。
Anar Sultanov

パーフェクト、これですべてが今ではもっと理にかなっています、ありがとうございました。私は数日のためにそれを試してみる機会を得ないかもしれないが、戻ってくると私は行くを持っていた後、正しい答えとしてこれをマークするようにしてくださいます
マット・ウィリアムズ

1
それはもう非難されましたlol ...少なくともUnAuthenticatedServerOAuth2AuthorizedClientRepositoryは...
SledgeHammer

@SledgeHammerに感謝します。回答を更新しました。
Anar Sultanov

1

@Anar Sultanovからの上記の回答はこの点に到達するのに役立ちましたが、OAuthトークンリクエストにいくつかの追加のヘッダーを追加する必要があったので、ユースケースの問題を解決する方法について完全な回答を提供すると思いました。

プロバイダーの詳細を構成する

以下を追加 application.properties

spring.security.oauth2.client.registration.uaa.client-id=${CLIENT_ID:}
spring.security.oauth2.client.registration.uaa.client-secret=${CLIENT_SECRET:}
spring.security.oauth2.client.registration.uaa.scope=${SCOPE:}
spring.security.oauth2.client.registration.uaa.authorization-grant-type=client_credentials
spring.security.oauth2.client.provider.uaa.token-uri=${UAA_URL:}

カスタムを実装する ReactiveOAuth2AccessTokenResponseClient

これはサーバー間の通信なので、を使用する必要がありますServerOAuth2AuthorizedClientExchangeFilterFunction。これReactiveOAuth2AuthorizedClientManagerは、非反応性ではなく、のみを受け入れますOAuth2AuthorizedClientManager。したがって、ReactiveOAuth2AuthorizedClientManager.setAuthorizedClientProvider()(プロバイダーにOAuth2リクエストの作成に使用するように)を使用する場合ReactiveOAuth2AuthorizedClientProviderは、non-reactiveではなくを指定する必要がありOAuth2AuthorizedClientProviderます。春のセキュリティリファレンスドキュメントに従って、非リアクティブDefaultClientCredentialsTokenResponseClientを使用する場合、.setRequestEntityConverter()メソッドを使用してOAuth2トークンリクエストを変更できますが、リアクティブ同等物WebClientReactiveClientCredentialsTokenResponseClientはこの機能を提供しないため、独自の機能を実装する必要があります(既存のWebClientReactiveClientCredentialsTokenResponseClientロジック)。

私の実装が呼び出されましたUaaWebClientReactiveClientCredentialsTokenResponseClient(いくつかの追加のヘッダー/本文フィールドを追加するためにheaders()およびbody()メソッドをデフォルトからほんの少し変更するだけなので、実装は省略されWebClientReactiveClientCredentialsTokenResponseClient、基礎となる認証フローは変更されません)。

構成、設定 WebClient

このServerOAuth2AuthorizedClientExchangeFilterFunction.setClientCredentialsTokenResponseClient()メソッドは廃止されたので、そのメソッドからの廃止のアドバイスに従ってください。

非推奨。代わりに使用してくださいServerOAuth2AuthorizedClientExchangeFilterFunction(ReactiveOAuth2AuthorizedClientManager)。でClientCredentialsReactiveOAuth2AuthorizedClientProvider構成されたWebClientReactiveClientCredentialsTokenResponseClient(またはカスタムの)のインスタンスを作成し、それをに提供しDefaultReactiveOAuth2AuthorizedClientManagerます。

これにより、次のような構成になります。

@Bean("oAuth2WebClient")
public WebClient oauthFilteredWebClient(final ReactiveClientRegistrationRepository 
    clientRegistrationRepository)
{
    final ClientCredentialsReactiveOAuth2AuthorizedClientProvider
        clientCredentialsReactiveOAuth2AuthorizedClientProvider =
            new ClientCredentialsReactiveOAuth2AuthorizedClientProvider();
    clientCredentialsReactiveOAuth2AuthorizedClientProvider.setAccessTokenResponseClient(
        new UaaWebClientReactiveClientCredentialsTokenResponseClient());

    final DefaultReactiveOAuth2AuthorizedClientManager defaultReactiveOAuth2AuthorizedClientManager =
        new DefaultReactiveOAuth2AuthorizedClientManager(clientRegistrationRepository,
            new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
    defaultReactiveOAuth2AuthorizedClientManager.setAuthorizedClientProvider(
        clientCredentialsReactiveOAuth2AuthorizedClientProvider);

    final ServerOAuth2AuthorizedClientExchangeFilterFunction oAuthFilter =
        new ServerOAuth2AuthorizedClientExchangeFilterFunction(defaultReactiveOAuth2AuthorizedClientManager);
    oAuthFilter.setDefaultClientRegistrationId("uaa");

    return WebClient.builder()
        .filter(oAuthFilter)
        .build();
}

WebClient通常通り使用

これで、oAuth2WebClientBeanを使用して、構成されたOAuth2プロバイダーによって保護されているリソースにアクセスする準備が整いましたWebClient


client-id、client-secret、およびoauthエンドポイントをプログラムで渡す方法は?
monti

私はこれを試していませんがClientRegistration、必要な詳細でのインスタンスを作成し、それらをのコンストラクタに渡すことができるようです InMemoryReactiveClientRegistrationRepository(のデフォルト実装ReactiveClientRegistrationRepository)。次に、メソッドに渡されるInMemoryReactiveClientRegistrationRepository自動配線の代わりに、新しく作成したBeanを使用しますclientRegistrationRepositoryoauthFilteredWebClient
Matt Williams

ええと、ClientRegistration実行時に別のユーザーを登録することはできませんか?私が理解している限りClientRegistration、起動時にのBeanを作成する必要があります。
monti

application.propertiesファイルでそれらを宣言しないようにしたいだけだと思いました。独自に実装するとReactiveOAuth2AccessTokenResponseClient、OAuth2トークンを取得するために必要なあらゆる要求を行うことができますが、要求ごとに動的な「コンテキスト」を提供する方法はわかりません。独自のフィルター全体を実装した場合も同様です。からのアクセスが許可されるので、そこから必要なものを推測できない限り、オプションは不明です。使用例は何ですか?起動時に可能な登録が不明なのはなぜですか?
Matt Williams

1

@matt Williamsの回答は非常に役に立ちました。誰かがプログラムでclientIdとWebClient構成のシークレットを渡したい場合に備えて追加します。これがどのようにしてできるかです。

 @Configuration
    public class WebClientConfig {

    public static final String TEST_REGISTRATION_ID = "test-client";

    @Bean
    public ReactiveClientRegistrationRepository clientRegistrationRepository() {
        var clientRegistration = ClientRegistration.withRegistrationId(TEST_REGISTRATION_ID)
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                .clientId("<client_id>")
                .clientSecret("<client_secret>")
                .tokenUri("<token_uri>")
                .build();
        return new InMemoryReactiveClientRegistrationRepository(clientRegistration);
    }

    @Bean
    public WebClient testWebClient(ReactiveClientRegistrationRepository clientRegistrationRepo) {

        var oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrationRepo,  new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
        oauth.setDefaultClientRegistrationId(TEST_REGISTRATION_ID);

        return WebClient.builder()
                .baseUrl("https://.test.com")
                .filter(oauth)
                .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
    }
}

0

こんにちは、手遅れかもしれませんが、RestTemplateはSpring Security 5で引き続きサポートされていますが、非反応性のアプリであるRestTemplateがまだ使用されています。

client_credentialsフローを使用するには、次の構成を使用します

application.yml

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: ${okta.oauth2.issuer}/v1/keys
      client:
        registration:
          okta:
            client-id: ${okta.oauth2.clientId}
            client-secret: ${okta.oauth2.clientSecret}
            scope: "custom-scope"
            authorization-grant-type: client_credentials
            provider: okta
        provider:
          okta:
            authorization-uri: ${okta.oauth2.issuer}/v1/authorize
            token-uri: ${okta.oauth2.issuer}/v1/token

OauthResTemplateの構成

@Configuration
@RequiredArgsConstructor
public class OAuthRestTemplateConfig {

    public static final String OAUTH_WEBCLIENT = "OAUTH_WEBCLIENT";

    private final RestTemplateBuilder restTemplateBuilder;
    private final OAuth2AuthorizedClientService oAuth2AuthorizedClientService;
    private final ClientRegistrationRepository clientRegistrationRepository;

    @Bean(OAUTH_WEBCLIENT)
    RestTemplate oAuthRestTemplate() {
        var clientRegistration = clientRegistrationRepository.findByRegistrationId(Constants.OKTA_AUTH_SERVER_ID);

        return restTemplateBuilder
                .additionalInterceptors(new OAuthClientCredentialsRestTemplateInterceptorConfig(authorizedClientManager(), clientRegistration))
                .setReadTimeout(Duration.ofSeconds(5))
                .setConnectTimeout(Duration.ofSeconds(1))
                .build();
    }

    @Bean
    OAuth2AuthorizedClientManager authorizedClientManager() {
        var authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
                .clientCredentials()
                .build();

        var authorizedClientManager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, oAuth2AuthorizedClientService);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

        return authorizedClientManager;
    }

}

インターセプター

public class OAuthClientCredentialsRestTemplateInterceptor implements ClientHttpRequestInterceptor {

    private final OAuth2AuthorizedClientManager manager;
    private final Authentication principal;
    private final ClientRegistration clientRegistration;

    public OAuthClientCredentialsRestTemplateInterceptor(OAuth2AuthorizedClientManager manager, ClientRegistration clientRegistration) {
        this.manager = manager;
        this.clientRegistration = clientRegistration;
        this.principal = createPrincipal();
    }

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        OAuth2AuthorizeRequest oAuth2AuthorizeRequest = OAuth2AuthorizeRequest
                .withClientRegistrationId(clientRegistration.getRegistrationId())
                .principal(principal)
                .build();
        OAuth2AuthorizedClient client = manager.authorize(oAuth2AuthorizeRequest);
        if (isNull(client)) {
            throw new IllegalStateException("client credentials flow on " + clientRegistration.getRegistrationId() + " failed, client is null");
        }

        request.getHeaders().add(HttpHeaders.AUTHORIZATION, BEARER_PREFIX + client.getAccessToken().getTokenValue());
        return execution.execute(request, body);
    }

    private Authentication createPrincipal() {
        return new Authentication() {
            @Override
            public Collection<? extends GrantedAuthority> getAuthorities() {
                return Collections.emptySet();
            }

            @Override
            public Object getCredentials() {
                return null;
            }

            @Override
            public Object getDetails() {
                return null;
            }

            @Override
            public Object getPrincipal() {
                return this;
            }

            @Override
            public boolean isAuthenticated() {
                return false;
            }

            @Override
            public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
            }

            @Override
            public String getName() {
                return clientRegistration.getClientId();
            }
        };
    }
}

これにより、最初の呼び出し時とトークンの有効期限が切れたときにaccess_tokenが生成されます。OAuth2AuthorizedClientManagerがこれをすべて管理します

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.