From aa2ae34aa5f3aeaa1294cb9bfed119bb8cd8faf0 Mon Sep 17 00:00:00 2001 From: Scott Frederick Date: Wed, 27 Nov 2019 11:14:20 -0600 Subject: [PATCH] Refactor OAuth2 configuration to use Spring Security 5.2. --- build.gradle | 2 +- .../configuration/CredHubTemplateFactory.java | 8 +- .../core/CredHubOAuth2RequestInterceptor.java | 92 +++--------------- .../core/CredHubRestTemplateFactory.java | 57 ++++++++--- .../credhub/core/CredHubTemplate.java | 8 +- .../credhub/core/CredHubWebClientFactory.java | 57 ++++++++--- ...eClientCredentialsTokenResponseClient.java | 97 ------------------- .../src/docs/asciidoc/getting-started.adoc | 10 +- .../src/test/resources/application-test.yml | 22 +++++ .../src/test/resources/application-test.yml | 22 +++++ .../CredHubOAuth2AutoConfiguration.java | 21 ++-- .../CredHubTemplateAutoConfiguration.java | 11 ++- .../CredHubOAuth2AutoConfigurationTests.java | 20 +++- 13 files changed, 197 insertions(+), 230 deletions(-) delete mode 100644 spring-credhub-core/src/main/java/org/springframework/credhub/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClient.java create mode 100644 spring-credhub-integration-tests/src/test/resources/application-test.yml create mode 100644 spring-credhub-reactive-integration-tests/src/test/resources/application-test.yml diff --git a/build.gradle b/build.gradle index 1c89da4..cb6862f 100644 --- a/build.gradle +++ b/build.gradle @@ -34,7 +34,7 @@ ext { springBootVersion = "2.2.1.RELEASE" springSecurityVersion = "5.2.1.RELEASE" springCloudConnectorsVersion = "1.2.5.RELEASE" - reactorVersion = "Dysprosium-SR1" + reactorVersion = "Dysprosium-SR2" okHttp3Version = "4.2.2" httpClientVersion = "4.5.10" diff --git a/spring-credhub-core/src/main/java/org/springframework/credhub/configuration/CredHubTemplateFactory.java b/spring-credhub-core/src/main/java/org/springframework/credhub/configuration/CredHubTemplateFactory.java index a3b1a98..799f2ab 100644 --- a/spring-credhub-core/src/main/java/org/springframework/credhub/configuration/CredHubTemplateFactory.java +++ b/spring-credhub-core/src/main/java/org/springframework/credhub/configuration/CredHubTemplateFactory.java @@ -23,9 +23,9 @@ import org.springframework.credhub.core.ReactiveCredHubTemplate; import org.springframework.credhub.support.ClientOptions; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.reactive.ClientHttpConnector; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; /** @@ -55,15 +55,15 @@ public class CredHubTemplateFactory { * @param credHubProperties connection properties * @param clientOptions connection options * @param clientRegistrationRepository a repository of OAuth2 client registrations - * @param authorizedClientService a repository of authorized OAuth2 clients + * @param authorizedClientRepository a repository of authorized OAuth2 clients * @return a {@code CredHubTemplate} */ public CredHubTemplate credHubTemplate(CredHubProperties credHubProperties, ClientOptions clientOptions, ClientRegistrationRepository clientRegistrationRepository, - OAuth2AuthorizedClientService authorizedClientService) { + OAuth2AuthorizedClientRepository authorizedClientRepository) { return new CredHubTemplate(credHubProperties, clientHttpRequestFactory(clientOptions), - clientRegistrationRepository, authorizedClientService); + clientRegistrationRepository, authorizedClientRepository); } /** diff --git a/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubOAuth2RequestInterceptor.java b/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubOAuth2RequestInterceptor.java index bb8eb36..560c587 100644 --- a/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubOAuth2RequestInterceptor.java +++ b/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubOAuth2RequestInterceptor.java @@ -23,20 +23,12 @@ import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.client.support.HttpRequestWrapper; import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; -import org.springframework.security.oauth2.client.endpoint.DefaultClientCredentialsTokenResponseClient; -import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2RefreshToken; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.web.client.RestOperations; import java.io.IOException; -import java.time.Clock; -import java.time.Duration; -import java.time.Instant; import java.util.Collections; /** @@ -46,20 +38,12 @@ import java.util.Collections; */ class CredHubOAuth2RequestInterceptor implements ClientHttpRequestInterceptor { private final ClientRegistration clientRegistration; - private final OAuth2AuthorizedClientService authorizedClientService; + private final OAuth2AuthorizedClientManager clientManager; - private final DefaultClientCredentialsTokenResponseClient clientCredentialsTokenResponseClient; - - private final Clock clock = Clock.systemUTC(); - private final Duration accessTokenExpiresSkew = Duration.ofMinutes(1); - - CredHubOAuth2RequestInterceptor(RestOperations tokenServerRestTemplate, - ClientRegistration clientRegistration, - OAuth2AuthorizedClientService authorizedClientService) { + CredHubOAuth2RequestInterceptor(ClientRegistration clientRegistration, + OAuth2AuthorizedClientManager clientManager) { this.clientRegistration = clientRegistration; - this.authorizedClientService = authorizedClientService; - - this.clientCredentialsTokenResponseClient = createClientCredentialsTokenResponseClient(tokenServerRestTemplate); + this.clientManager = clientManager; } /** @@ -73,78 +57,30 @@ class CredHubOAuth2RequestInterceptor implements ClientHttpRequestInterceptor { HttpRequestWrapper requestWrapper = new HttpRequestWrapper(request); HttpHeaders headers = requestWrapper.getHeaders(); - headers.setBearerAuth(getAccessToken().getTokenValue()); + headers.setBearerAuth(authorizeClient().getAccessToken().getTokenValue()); return execution.execute(requestWrapper, body); } - private OAuth2AccessToken getAccessToken() { - OAuth2AuthorizedClient client = authorizedClientService - .loadAuthorizedClient(clientRegistration.getRegistrationId(), clientRegistration.getClientId()); - - if (client == null || tokenExpiring(client)) { - client = authorizeClient(); - } - - return client.getAccessToken(); - } - private OAuth2AuthorizedClient authorizeClient() { - OAuth2ClientCredentialsGrantRequest request = - new OAuth2ClientCredentialsGrantRequest(clientRegistration); - OAuth2AccessTokenResponse tokenResponse = clientCredentialsTokenResponseClient.getTokenResponse(request); - - OAuth2AccessToken accessToken = tokenResponse.getAccessToken(); - OAuth2RefreshToken refreshToken = tokenResponse.getRefreshToken(); - - OAuth2AuthorizedClient authorizedClient = - new OAuth2AuthorizedClient(clientRegistration, - clientRegistration.getClientId(), - accessToken, refreshToken); - - saveAuthorizedClient(clientRegistration, accessToken, authorizedClient); - - return authorizedClient; - } - - private boolean tokenExpiring(OAuth2AuthorizedClient client) { - Instant now = this.clock.instant(); - Instant expiresAt = client.getAccessToken().getExpiresAt(); - if (expiresAt != null && now.isAfter(expiresAt.minus(this.accessTokenExpiresSkew))) { - return true; - } - return false; - } - - private void saveAuthorizedClient(ClientRegistration clientRegistration, - OAuth2AccessToken accessToken, - OAuth2AuthorizedClient authorizedClient) { - OAuth2ClientCredentialsGrantAuthenticationToken authentication = - new OAuth2ClientCredentialsGrantAuthenticationToken(clientRegistration, accessToken); - authorizedClientService.saveAuthorizedClient(authorizedClient, authentication); - } - - private static DefaultClientCredentialsTokenResponseClient createClientCredentialsTokenResponseClient(RestOperations restTemplate) { - DefaultClientCredentialsTokenResponseClient clientCredentialsTokenResponseClient = - new DefaultClientCredentialsTokenResponseClient(); - clientCredentialsTokenResponseClient.setRestOperations(restTemplate); - return clientCredentialsTokenResponseClient; + OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest + .withClientRegistrationId(clientRegistration.getRegistrationId()) + .principal(new OAuth2ClientCredentialsGrantAuthenticationToken(clientRegistration)) + .build(); + return clientManager.authorize(authorizeRequest); } private static class OAuth2ClientCredentialsGrantAuthenticationToken extends AbstractAuthenticationToken { private final ClientRegistration clientRegistration; - private final OAuth2AccessToken accessToken; - OAuth2ClientCredentialsGrantAuthenticationToken(ClientRegistration clientRegistration, - OAuth2AccessToken accessToken) { + OAuth2ClientCredentialsGrantAuthenticationToken(ClientRegistration clientRegistration) { super(Collections.emptyList()); this.clientRegistration = clientRegistration; - this.accessToken = accessToken; } @Override public Object getCredentials() { - return accessToken.getTokenValue(); + return null; } @Override diff --git a/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubRestTemplateFactory.java b/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubRestTemplateFactory.java index 600de7d..8fbe4b2 100644 --- a/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubRestTemplateFactory.java +++ b/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubRestTemplateFactory.java @@ -34,12 +34,18 @@ import org.springframework.http.converter.ByteArrayHttpMessageConverter; import org.springframework.http.converter.FormHttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder; +import org.springframework.security.oauth2.client.endpoint.DefaultClientCredentialsTokenResponseClient; +import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; +import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest; import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; -import org.springframework.web.client.RestOperations; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.DefaultUriBuilderFactory; @@ -76,20 +82,21 @@ class CredHubRestTemplateFactory { * @param clientHttpRequestFactory the {@link ClientHttpRequestFactory} to use when * creating new connections * @param clientRegistrationRepository a repository of OAuth2 client registrations - * @param authorizedClientService a repository of authorized OAuth2 clients + * @param authorizedClientRepository a repository of authorized OAuth2 clients * @return a configured {@link RestTemplate} */ static RestTemplate createRestTemplate(CredHubProperties properties, ClientHttpRequestFactory clientHttpRequestFactory, ClientRegistrationRepository clientRegistrationRepository, - OAuth2AuthorizedClientService authorizedClientService) { + OAuth2AuthorizedClientRepository authorizedClientRepository) { RestTemplate restTemplate = new RestTemplate(); configureRestTemplate(restTemplate, properties.getUrl(), clientHttpRequestFactory); - configureOAuth2(restTemplate, clientHttpRequestFactory, + configureOAuth2(restTemplate, + clientHttpRequestFactory, properties.getOauth2().getRegistrationId(), clientRegistrationRepository, - authorizedClientService); + authorizedClientRepository); return restTemplate; } @@ -121,13 +128,13 @@ class CredHubRestTemplateFactory { * creating new connections * @param clientId the OAuth2 client ID for authentication * @param clientRegistrationRepository a repository of OAuth2 client registrations - * @param authorizedClientService a repository of authorized OAuth2 clients + * @param authorizedClientRepository a repository of authorized OAuth2 clients */ private static void configureOAuth2(RestTemplate restTemplate, ClientHttpRequestFactory clientHttpRequestFactory, String clientId, ClientRegistrationRepository clientRegistrationRepository, - OAuth2AuthorizedClientService authorizedClientService) { + OAuth2AuthorizedClientRepository authorizedClientRepository) { ClientRegistration clientRegistration = clientRegistrationRepository.findByRegistrationId(clientId); if (clientRegistration == null) { @@ -135,11 +142,39 @@ class CredHubRestTemplateFactory { "' is not a valid Spring Security OAuth2 client registration"); } - RestOperations tokenServerRestTemplate = createTokenServerRestTemplate(clientHttpRequestFactory); + OAuth2AuthorizedClientManager clientManager = + buildClientManager(clientRegistrationRepository, authorizedClientRepository, clientHttpRequestFactory); restTemplate.getInterceptors() - .add(new CredHubOAuth2RequestInterceptor(tokenServerRestTemplate, - clientRegistration, authorizedClientService)); + .add(new CredHubOAuth2RequestInterceptor(clientRegistration, clientManager)); + } + + private static OAuth2AuthorizedClientManager buildClientManager( + ClientRegistrationRepository clientRegistrationRepository, + OAuth2AuthorizedClientRepository authorizedClientRepository, + ClientHttpRequestFactory clientHttpRequestFactory) { + + OAuth2AuthorizedClientProvider authorizedClientProvider = + OAuth2AuthorizedClientProviderBuilder.builder() + .authorizationCode() + .clientCredentials(b -> + b.accessTokenResponseClient(buildTokenResponseClient(clientHttpRequestFactory))) + .build(); + + DefaultOAuth2AuthorizedClientManager authorizedClientManager = + new DefaultOAuth2AuthorizedClientManager( + clientRegistrationRepository, authorizedClientRepository); + authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); + + return authorizedClientManager; + } + + private static OAuth2AccessTokenResponseClient buildTokenResponseClient( + ClientHttpRequestFactory clientHttpRequestFactory) { + DefaultClientCredentialsTokenResponseClient tokenResponseClient = + new DefaultClientCredentialsTokenResponseClient(); + tokenResponseClient.setRestOperations(createTokenServerRestTemplate(clientHttpRequestFactory)); + return tokenResponseClient; } private static RestTemplate createTokenServerRestTemplate(ClientHttpRequestFactory clientHttpRequestFactory) { diff --git a/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubTemplate.java b/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubTemplate.java index 97bb76c..604aac7 100644 --- a/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubTemplate.java +++ b/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubTemplate.java @@ -29,8 +29,8 @@ import org.springframework.credhub.core.permission.CredHubPermissionTemplate; import org.springframework.credhub.core.permissionV2.CredHubPermissionV2Operations; import org.springframework.credhub.core.permissionV2.CredHubPermissionV2Template; import org.springframework.http.client.ClientHttpRequestFactory; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; import org.springframework.util.Assert; import org.springframework.web.client.HttpStatusCodeException; import org.springframework.web.client.RestTemplate; @@ -82,18 +82,18 @@ public class CredHubTemplate implements CredHubOperations { * @param clientHttpRequestFactory the {@link ClientHttpRequestFactory} to use when * creating new connections * @param clientRegistrationRepository a repository of OAuth2 client registrations - * @param authorizedClientService a repository of authorized OAuth2 clients + * @param authorizedClientRepository a repository of authorized OAuth2 clients */ public CredHubTemplate(CredHubProperties properties, ClientHttpRequestFactory clientHttpRequestFactory, ClientRegistrationRepository clientRegistrationRepository, - OAuth2AuthorizedClientService authorizedClientService) { + OAuth2AuthorizedClientRepository authorizedClientRepository) { Assert.notNull(properties, "properties must not be null"); Assert.notNull(clientHttpRequestFactory, "clientHttpRequestFactory must not be null"); Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository must not be null"); this.restTemplate = CredHubRestTemplateFactory.createRestTemplate(properties, - clientHttpRequestFactory, clientRegistrationRepository, authorizedClientService); + clientHttpRequestFactory, clientRegistrationRepository, authorizedClientRepository); this.usingOAuth2 = true; } diff --git a/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubWebClientFactory.java b/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubWebClientFactory.java index 7ae34c3..29e0f2d 100644 --- a/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubWebClientFactory.java +++ b/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubWebClientFactory.java @@ -17,7 +17,6 @@ package org.springframework.credhub.core; import com.fasterxml.jackson.databind.ObjectMapper; -import org.springframework.credhub.security.oauth2.client.endpoint.WebClientReactiveClientCredentialsTokenResponseClient; import org.springframework.credhub.support.utils.JsonUtils; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; @@ -25,7 +24,11 @@ import org.springframework.http.client.reactive.ClientHttpConnector; import org.springframework.http.codec.CodecConfigurer; import org.springframework.http.codec.json.Jackson2JsonDecoder; import org.springframework.http.codec.json.Jackson2JsonEncoder; +import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProvider; +import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProviderBuilder; +import org.springframework.security.oauth2.client.endpoint.WebClientReactiveClientCredentialsTokenResponseClient; import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; +import org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2AuthorizedClientManager; import org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction; import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; import org.springframework.web.reactive.function.client.ExchangeStrategies; @@ -56,26 +59,24 @@ class CredHubWebClientFactory { /** * Create a {@link WebClient} configured for communication with a CredHub server. * - * @param properties CredHub connection properties - * @param clientHttpConnector the {@link ClientHttpConnector} to use when - * creating new connections + * @param properties CredHub connection properties + * @param clientHttpConnector the {@link ClientHttpConnector} to use when + * creating new connections * @param clientRegistrationRepository a repository of OAuth2 client registrations - * @param authorizedClientRepository a repository of OAuth2 authorized clients + * @param authorizedClientRepository a repository of OAuth2 authorized clients * @return a configured {@link WebClient} */ static WebClient createWebClient(CredHubProperties properties, ClientHttpConnector clientHttpConnector, ReactiveClientRegistrationRepository clientRegistrationRepository, ServerOAuth2AuthorizedClientRepository authorizedClientRepository) { - WebClientReactiveClientCredentialsTokenResponseClient tokenResponseClient = - new WebClientReactiveClientCredentialsTokenResponseClient(); - tokenResponseClient.setWebClient(WebClient.builder() - .clientConnector(clientHttpConnector) - .build()); + ReactiveOAuth2AuthorizedClientProvider clientProvider = + buildClientProvider(clientHttpConnector); + + DefaultReactiveOAuth2AuthorizedClientManager clientManager = + buildClientManager(clientRegistrationRepository, authorizedClientRepository, clientProvider); ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = - new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrationRepository, - authorizedClientRepository); - oauth.setClientCredentialsTokenResponseClient(tokenResponseClient); + new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientManager); return buildWebClient(properties.getUrl(), clientHttpConnector) .filter(oauth) @@ -86,6 +87,36 @@ class CredHubWebClientFactory { .build(); } + private static ReactiveOAuth2AuthorizedClientProvider buildClientProvider( + ClientHttpConnector clientHttpConnector) { + return ReactiveOAuth2AuthorizedClientProviderBuilder.builder() + .authorizationCode() + .clientCredentials(b -> + b.accessTokenResponseClient(buildTokenResponseClient(clientHttpConnector))) + .build(); + } + + private static WebClientReactiveClientCredentialsTokenResponseClient buildTokenResponseClient( + ClientHttpConnector clientHttpConnector) { + WebClientReactiveClientCredentialsTokenResponseClient tokenResponseClient = + new WebClientReactiveClientCredentialsTokenResponseClient(); + tokenResponseClient.setWebClient(WebClient.builder() + .clientConnector(clientHttpConnector) + .build()); + return tokenResponseClient; + } + + private static DefaultReactiveOAuth2AuthorizedClientManager buildClientManager( + ReactiveClientRegistrationRepository clientRegistrationRepository, + ServerOAuth2AuthorizedClientRepository authorizedClientRepository, + ReactiveOAuth2AuthorizedClientProvider clientProvider) { + DefaultReactiveOAuth2AuthorizedClientManager clientManager = + new DefaultReactiveOAuth2AuthorizedClientManager(clientRegistrationRepository, + authorizedClientRepository); + clientManager.setAuthorizedClientProvider(clientProvider); + return clientManager; + } + private static WebClient.Builder buildWebClient(String baseUri, ClientHttpConnector clientHttpConnector) { ExchangeStrategies strategies = ExchangeStrategies.builder() .codecs(configurer -> { diff --git a/spring-credhub-core/src/main/java/org/springframework/credhub/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClient.java b/spring-credhub-core/src/main/java/org/springframework/credhub/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClient.java deleted file mode 100644 index 074c437..0000000 --- a/spring-credhub-core/src/main/java/org/springframework/credhub/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClient.java +++ /dev/null @@ -1,97 +0,0 @@ -package org.springframework.credhub.security.oauth2.client.endpoint; - -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; -import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; -import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest; -import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; -import org.springframework.web.reactive.function.BodyInserters; -import org.springframework.web.reactive.function.client.WebClient; -import reactor.core.publisher.Mono; - -import java.util.Set; -import java.util.function.Consumer; - -import static org.springframework.security.oauth2.core.web.reactive.function.OAuth2BodyExtractors.oauth2AccessTokenResponse; - -/** - * An implementation of an {@link ReactiveOAuth2AccessTokenResponseClient} that "exchanges" - * an authorization code credential for an access token credential - * at the Authorization Server's Token Endpoint. - * - * @author Rob Winch - * @since 5.1 - * @see OAuth2AccessTokenResponseClient - * @see OAuth2AuthorizationCodeGrantRequest - * @see OAuth2AccessTokenResponse - * @see Nimbus OAuth 2.0 SDK - * @see Section 4.1.3 Access Token Request (Authorization Code Grant) - * @see Section 4.1.4 Access Token Response (Authorization Code Grant) - */ -public class WebClientReactiveClientCredentialsTokenResponseClient implements ReactiveOAuth2AccessTokenResponseClient { - private WebClient webClient = WebClient.builder() - .build(); - - @Override - public Mono getTokenResponse(OAuth2ClientCredentialsGrantRequest authorizationGrantRequest) { - return Mono.defer(() -> { - ClientRegistration clientRegistration = authorizationGrantRequest.getClientRegistration(); - - String tokenUri = clientRegistration.getProviderDetails().getTokenUri(); - BodyInserters.FormInserter body = body(authorizationGrantRequest); - - return this.webClient.post() - .uri(tokenUri) - .accept(MediaType.APPLICATION_JSON) - .headers(headers(clientRegistration)) - .body(body) - .exchange() - .flatMap(response -> response.body(oauth2AccessTokenResponse())) - .map(response -> { - if (response.getAccessToken().getScopes().isEmpty()) { - response = OAuth2AccessTokenResponse.withResponse(response) - .scopes(authorizationGrantRequest.getClientRegistration().getScopes()) - .build(); - } - return response; - }); - }); - } - - private Consumer headers(ClientRegistration clientRegistration) { - return headers -> { - headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret()); - if (ClientAuthenticationMethod.BASIC.equals(clientRegistration.getClientAuthenticationMethod())) { - headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret()); - } - }; - } - - private static BodyInserters.FormInserter body(OAuth2ClientCredentialsGrantRequest authorizationGrantRequest) { - ClientRegistration clientRegistration = authorizationGrantRequest.getClientRegistration(); - BodyInserters.FormInserter body = BodyInserters - .fromFormData(OAuth2ParameterNames.GRANT_TYPE, authorizationGrantRequest.getGrantType().getValue()); - Set scopes = clientRegistration.getScopes(); - if (!CollectionUtils.isEmpty(scopes)) { - String scope = StringUtils.collectionToDelimitedString(scopes, " "); - body.with(OAuth2ParameterNames.SCOPE, scope); - } - if (ClientAuthenticationMethod.POST.equals(clientRegistration.getClientAuthenticationMethod())) { - body.with(OAuth2ParameterNames.CLIENT_ID, clientRegistration.getClientId()); - body.with(OAuth2ParameterNames.CLIENT_SECRET, clientRegistration.getClientSecret()); - } - return body; - } - - public void setWebClient(WebClient webClient) { - this.webClient = webClient; - } -} \ No newline at end of file diff --git a/spring-credhub-docs/src/docs/asciidoc/getting-started.adoc b/spring-credhub-docs/src/docs/asciidoc/getting-started.adoc index 32a5954..8d116ab 100644 --- a/spring-credhub-docs/src/docs/asciidoc/getting-started.adoc +++ b/spring-credhub-docs/src/docs/asciidoc/getting-started.adoc @@ -38,12 +38,12 @@ To use OAuth2 authentication to CredHub, add the following {spring-security}[Spr org.springframework.security spring-security-config - 5.1.0.RELEASE + 5.2.1.RELEASE org.springframework.security spring-security-oauth2-client - 5.1.0.RELEASE + 5.2.1.RELEASE @@ -58,13 +58,13 @@ Add the Spring CredHub starter to the `dependencies` section of the build file: To enable reactive support in Spring CredHub, add the following {spring-webflux}[Spring WebFlux] dependency to the build file: dependencies { - compile("org.springframework.boot:spring-boot-starter-webflux:5.1.0.RELEASE") + compile("org.springframework.boot:spring-boot-starter-webflux:5.2.1.RELEASE") } To use OAuth2 authentication to CredHub, add the following {spring-security}[Spring Security] dependencies to the build file: dependencies { - compile("org.springframework.security:spring-security-config:5.1.0.RELEASE") - compile("org.springframework.security:spring-security-oauth2-client:5.1.0.RELEASE") + compile("org.springframework.security:spring-security-config:5.2.1.RELEASE") + compile("org.springframework.security:spring-security-oauth2-client:5.2.1.RELEASE") } diff --git a/spring-credhub-integration-tests/src/test/resources/application-test.yml b/spring-credhub-integration-tests/src/test/resources/application-test.yml new file mode 100644 index 0000000..5443050 --- /dev/null +++ b/spring-credhub-integration-tests/src/test/resources/application-test.yml @@ -0,0 +1,22 @@ +spring: + credhub: + url: ${CREDHUB_SERVER} + ca-cert-files: + - ${CREDHUB_CA_CERT} + oauth2: + registration-id: credhub-test + security: + oauth2: + client: + registration: + credhub-test: + provider: uaa + client-id: ${CREDHUB_CLIENT} + client-secret: ${CREDHUB_SECRET} + authorization-grant-type: client_credentials + provider: + uaa: + token-uri: ${UAA_SERVER}/oauth/token + +debug: true +logging.level.org.springframework.web: DEBUG diff --git a/spring-credhub-reactive-integration-tests/src/test/resources/application-test.yml b/spring-credhub-reactive-integration-tests/src/test/resources/application-test.yml new file mode 100644 index 0000000..5443050 --- /dev/null +++ b/spring-credhub-reactive-integration-tests/src/test/resources/application-test.yml @@ -0,0 +1,22 @@ +spring: + credhub: + url: ${CREDHUB_SERVER} + ca-cert-files: + - ${CREDHUB_CA_CERT} + oauth2: + registration-id: credhub-test + security: + oauth2: + client: + registration: + credhub-test: + provider: uaa + client-id: ${CREDHUB_CLIENT} + client-secret: ${CREDHUB_SECRET} + authorization-grant-type: client_credentials + provider: + uaa: + token-uri: ${UAA_SERVER}/oauth/token + +debug: true +logging.level.org.springframework.web: DEBUG diff --git a/spring-credhub-starter/src/main/java/org/springframework/credhub/autoconfig/CredHubOAuth2AutoConfiguration.java b/spring-credhub-starter/src/main/java/org/springframework/credhub/autoconfig/CredHubOAuth2AutoConfiguration.java index 1ba5b83..2d2ecab 100644 --- a/spring-credhub-starter/src/main/java/org/springframework/credhub/autoconfig/CredHubOAuth2AutoConfiguration.java +++ b/spring-credhub-starter/src/main/java/org/springframework/credhub/autoconfig/CredHubOAuth2AutoConfiguration.java @@ -16,7 +16,7 @@ package org.springframework.credhub.autoconfig; -import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -24,17 +24,19 @@ import org.springframework.boot.autoconfigure.security.oauth2.client.ClientsConf import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties; import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesRegistrationAdapter; import org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration; +import org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; +import org.springframework.security.oauth2.client.web.AuthenticatedPrincipalOAuth2AuthorizedClientRepository; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.client.web.server.UnAuthenticatedServerOAuth2AuthorizedClientRepository; @@ -48,7 +50,7 @@ import java.util.List; */ @Configuration @EnableConfigurationProperties(OAuth2ClientProperties.class) -@AutoConfigureBefore(ReactiveOAuth2ClientAutoConfiguration.class) +@AutoConfigureAfter({OAuth2ClientAutoConfiguration.class, ReactiveOAuth2ClientAutoConfiguration.class}) @ConditionalOnClass(name = "org.springframework.security.oauth2.client.registration.ClientRegistration") @Conditional(ClientsConfiguredCondition.class) public class CredHubOAuth2AutoConfiguration { @@ -67,6 +69,7 @@ public class CredHubOAuth2AutoConfiguration { */ @Bean @ConditionalOnMissingBean + @ConditionalOnClass(name = "javax.servlet.http.HttpServletRequest") public ClientRegistrationRepository credHubClientRegistrationRepository() { List registrations = new ArrayList<>( OAuth2ClientPropertiesRegistrationAdapter @@ -75,17 +78,19 @@ public class CredHubOAuth2AutoConfiguration { } /** - * Create an {@code OAuth2AuthorizedClientService} bean for use with an OAuth2-enabled + * Create an {@code OAuth2AuthorizedClientRepository} bean for use with an OAuth2-enabled * {@code CredHubTemplate}. * * @param clientRegistrationRepository a {@code ClientRegistrationRepository} - * @return the {@code OAuth2AuthorizedClientService} + * @return the {@code OAuth2AuthorizedClientRepository} */ @Bean @ConditionalOnMissingBean - public OAuth2AuthorizedClientService credHubAuthorizedClientService( + @ConditionalOnClass(name = "javax.servlet.http.HttpServletRequest") + public OAuth2AuthorizedClientRepository credHubAuthorizedClientRepository( ClientRegistrationRepository clientRegistrationRepository) { - return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository); + return new AuthenticatedPrincipalOAuth2AuthorizedClientRepository( + new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository)); } /** @@ -115,7 +120,7 @@ public class CredHubOAuth2AutoConfiguration { @Bean @ConditionalOnMissingBean @ConditionalOnClass(name = "org.springframework.web.reactive.function.client.WebClient") - public ServerOAuth2AuthorizedClientRepository credHubAuthorizedClientRepository() { + public ServerOAuth2AuthorizedClientRepository credHubReactiveAuthorizedClientRepository() { return new UnAuthenticatedServerOAuth2AuthorizedClientRepository(); } } \ No newline at end of file diff --git a/spring-credhub-starter/src/main/java/org/springframework/credhub/autoconfig/CredHubTemplateAutoConfiguration.java b/spring-credhub-starter/src/main/java/org/springframework/credhub/autoconfig/CredHubTemplateAutoConfiguration.java index 80af343..66a96ac 100644 --- a/spring-credhub-starter/src/main/java/org/springframework/credhub/autoconfig/CredHubTemplateAutoConfiguration.java +++ b/spring-credhub-starter/src/main/java/org/springframework/credhub/autoconfig/CredHubTemplateAutoConfiguration.java @@ -33,9 +33,9 @@ import org.springframework.credhub.core.CredHubTemplate; import org.springframework.credhub.core.ReactiveCredHubOperations; import org.springframework.credhub.core.ReactiveCredHubTemplate; import org.springframework.credhub.support.ClientOptions; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; /** @@ -60,26 +60,27 @@ public class CredHubTemplateAutoConfiguration { * @param credHubProperties {@link CredHubProperties} for CredHub * @param clientOptions client connection options * @param clientRegistrationRepository a repository of OAuth2 client registrations - * @param authorizedClientService a repository of authorized OAuth2 clients + * @param authorizedClientRepository a repository of authorized OAuth2 clients * @return the {@link CredHubOperations} bean */ @Bean @ConditionalOnMissingBean + @ConditionalOnClass(name = "javax.servlet.http.HttpServletRequest") public CredHubOperations credHubTemplate( CredHubProperties credHubProperties, ClientOptions clientOptions, @Autowired(required = false) ClientRegistrationRepository clientRegistrationRepository, - @Autowired(required = false) OAuth2AuthorizedClientService authorizedClientService) { + @Autowired(required = false) OAuth2AuthorizedClientRepository authorizedClientRepository) { if (credHubProperties.getOauth2() == null || credHubProperties.getOauth2().getRegistrationId() == null) { return credHubTemplateFactory.credHubTemplate(credHubProperties, clientOptions); } - if (clientRegistrationRepository == null || authorizedClientService == null) { + if (clientRegistrationRepository == null || authorizedClientRepository == null) { throw misconfiguredException(); } return credHubTemplateFactory.credHubTemplate(credHubProperties, clientOptions, - clientRegistrationRepository, authorizedClientService); + clientRegistrationRepository, authorizedClientRepository); } /** diff --git a/spring-credhub-starter/src/test/java/org/springframework/credhub/autoconfig/CredHubOAuth2AutoConfigurationTests.java b/spring-credhub-starter/src/test/java/org/springframework/credhub/autoconfig/CredHubOAuth2AutoConfigurationTests.java index e380c83..0cffac4 100644 --- a/spring-credhub-starter/src/test/java/org/springframework/credhub/autoconfig/CredHubOAuth2AutoConfigurationTests.java +++ b/spring-credhub-starter/src/test/java/org/springframework/credhub/autoconfig/CredHubOAuth2AutoConfigurationTests.java @@ -95,6 +95,18 @@ public class CredHubOAuth2AutoConfigurationTests { }); } + @Test + public void oauth2ContextConfiguredWithReactiveWebAppNoServlet() { + new ReactiveWebApplicationContextRunner() + .withClassLoader(new FilteredClassLoader("javax.servlet")) + .withConfiguration(AutoConfigurations.of(configurations)) + .withPropertyValues(oAuth2ClientProperties) + .run(context -> { + assertServletOAuth2ContextNotConfigured(context); + assertReactiveOAuth2ContextConfigured(context); + }); + } + @Test public void oauth2ContextNotConfiguredWithoutProperties() { context @@ -112,28 +124,28 @@ public class CredHubOAuth2AutoConfigurationTests { ApplicationContextAssertProvider context) { assertThat(context).hasBean("credHubClientRegistrationRepository"); - assertThat(context).hasBean("credHubAuthorizedClientService"); + assertThat(context).hasBean("credHubAuthorizedClientRepository"); } private void assertServletOAuth2ContextNotConfigured( ApplicationContextAssertProvider context) { assertThat(context).doesNotHaveBean("credHubClientRegistrationRepository"); - assertThat(context).doesNotHaveBean("credHubAuthorizedClientService"); + assertThat(context).doesNotHaveBean("credHubAuthorizedClientRepository"); } private void assertReactiveOAuth2ContextConfigured( ApplicationContextAssertProvider context) { assertThat(context).hasBean("credHubReactiveClientRegistrationRepository"); - assertThat(context).hasBean("credHubAuthorizedClientRepository"); + assertThat(context).hasBean("credHubReactiveAuthorizedClientRepository"); } private void assertReactiveOAuth2ContextNotConfigured( ApplicationContextAssertProvider context) { assertThat(context).doesNotHaveBean("credHubReactiveClientRegistrationRepository"); - assertThat(context).doesNotHaveBean("credHubAuthorizedClientRepository"); + assertThat(context).doesNotHaveBean("credHubReactiveAuthorizedClientRepository"); } private void assertOAuth2ContextNotConfigured(