web-dev-qa-db-ja.com

OAuth2RestTemplateのSpring Security 5の置き換え

spring-security-oauth2:2.4.0.RELEASEクラスでは、OAuth2RestTemplateOAuth2ProtectedResourceDetailsClientCredentialsAccessTokenProviderなどのクラスはすべて非推奨としてマークされています。

これらのクラスのjavadocから、 Spring Security Migration Guide が示されています。これは、コアSpring-Security 5プロジェクトに移行する必要があることを示唆しています。ただし、このプロジェクトでユースケースを実装する方法を見つけるのに苦労しています。

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

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

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

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

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

13
Matt Williams

こんにちは多分手遅れかもしれませんが、RestTemplateはまだSpring Security 5でサポートされています非反応のアプリRestTemplateはまだ使用されています必要なことは、Spring Securityを適切に構成し、移行ガイドに記載されているインターセプターを作成することだけです。

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がこれをすべて管理します

0
Leandro Assis

@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
Jogger