Refactor OAuth2 configuration to use Spring Security 5.2.

This commit is contained in:
Scott Frederick
2019-11-27 11:14:20 -06:00
parent 8f59123b1c
commit aa2ae34aa5
13 changed files with 197 additions and 230 deletions

View File

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

View File

@@ -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);
}
/**

View File

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

View File

@@ -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<OAuth2ClientCredentialsGrantRequest> buildTokenResponseClient(
ClientHttpRequestFactory clientHttpRequestFactory) {
DefaultClientCredentialsTokenResponseClient tokenResponseClient =
new DefaultClientCredentialsTokenResponseClient();
tokenResponseClient.setRestOperations(createTokenServerRestTemplate(clientHttpRequestFactory));
return tokenResponseClient;
}
private static RestTemplate createTokenServerRestTemplate(ClientHttpRequestFactory clientHttpRequestFactory) {

View File

@@ -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;
}

View File

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

View File

@@ -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 &quot;exchanges&quot;
* 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 <a target="_blank" href="https://connect2id.com/products/nimbus-oauth-openid-connect-sdk">Nimbus OAuth 2.0 SDK</a>
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request (Authorization Code Grant)</a>
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response (Authorization Code Grant)</a>
*/
public class WebClientReactiveClientCredentialsTokenResponseClient implements ReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> {
private WebClient webClient = WebClient.builder()
.build();
@Override
public Mono<OAuth2AccessTokenResponse> getTokenResponse(OAuth2ClientCredentialsGrantRequest authorizationGrantRequest) {
return Mono.defer(() -> {
ClientRegistration clientRegistration = authorizationGrantRequest.getClientRegistration();
String tokenUri = clientRegistration.getProviderDetails().getTokenUri();
BodyInserters.FormInserter<String> 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<HttpHeaders> 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<String> body(OAuth2ClientCredentialsGrantRequest authorizationGrantRequest) {
ClientRegistration clientRegistration = authorizationGrantRequest.getClientRegistration();
BodyInserters.FormInserter<String> body = BodyInserters
.fromFormData(OAuth2ParameterNames.GRANT_TYPE, authorizationGrantRequest.getGrantType().getValue());
Set<String> 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;
}
}

View File

@@ -38,12 +38,12 @@ To use OAuth2 authentication to CredHub, add the following {spring-security}[Spr
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.1.0.RELEASE</version>
<version>5.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
<version>5.1.0.RELEASE</version>
<version>5.2.1.RELEASE</version>
</dependency>
</dependencies>
@@ -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")
}

View File

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

View File

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

View File

@@ -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<ClientRegistration> 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();
}
}

View File

@@ -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);
}
/**

View File

@@ -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<? extends ApplicationContext> context) {
assertThat(context).hasBean("credHubClientRegistrationRepository");
assertThat(context).hasBean("credHubAuthorizedClientService");
assertThat(context).hasBean("credHubAuthorizedClientRepository");
}
private void assertServletOAuth2ContextNotConfigured(
ApplicationContextAssertProvider<? extends ApplicationContext> context) {
assertThat(context).doesNotHaveBean("credHubClientRegistrationRepository");
assertThat(context).doesNotHaveBean("credHubAuthorizedClientService");
assertThat(context).doesNotHaveBean("credHubAuthorizedClientRepository");
}
private void assertReactiveOAuth2ContextConfigured(
ApplicationContextAssertProvider<? extends ApplicationContext> context) {
assertThat(context).hasBean("credHubReactiveClientRegistrationRepository");
assertThat(context).hasBean("credHubAuthorizedClientRepository");
assertThat(context).hasBean("credHubReactiveAuthorizedClientRepository");
}
private void assertReactiveOAuth2ContextNotConfigured(
ApplicationContextAssertProvider<? extends ApplicationContext> context) {
assertThat(context).doesNotHaveBean("credHubReactiveClientRegistrationRepository");
assertThat(context).doesNotHaveBean("credHubAuthorizedClientRepository");
assertThat(context).doesNotHaveBean("credHubReactiveAuthorizedClientRepository");
}
private void assertOAuth2ContextNotConfigured(