From 0ac851f7f12bd9955c6a56c20be91f010702cf19 Mon Sep 17 00:00:00 2001 From: Ivo Smid Date: Tue, 2 Jun 2020 20:34:27 +0200 Subject: [PATCH] Make OAuth's RestTemplate and WebClient customizable --- .../OAuth2ClientConfiguration.java | 42 +++++++++-- .../oauth2/client/OAuth2ClientConfigurer.java | 19 +++-- .../oauth2/client/OAuth2LoginConfigurer.java | 43 ++++++++--- .../OAuth2ResourceServerConfigurer.java | 34 +++++++-- .../ReactiveOAuth2ClientImportSelector.java | 33 +++++++-- ...th2ResourceServerBeanDefinitionParser.java | 13 +++- .../config/web/server/ServerHttpSecurity.java | 44 ++++++++---- .../config/web/server/ServerOpaqueTokenDsl.kt | 11 +++ .../oauth2/login/UserInfoEndpointDsl.kt | 4 ++ .../servlet/oauth2/resourceserver/JwtDsl.kt | 9 +++ gradle/dependency-management.gradle | 2 +- ...OAuth2AuthorizedClientProviderBuilder.java | 39 ++++++---- ...OAuth2AuthorizedClientProviderBuilder.java | 42 +++++++---- ...activeOAuth2AccessTokenResponseClient.java | 2 +- ...tAuthorizationCodeTokenResponseClient.java | 13 ++-- ...tClientCredentialsTokenResponseClient.java | 13 ++-- .../DefaultPasswordTokenResponseClient.java | 13 ++-- ...efaultRefreshTokenTokenResponseClient.java | 13 ++-- .../endpoint/OAuth2RestTemplateFactory.java | 72 +++++++++++++++++++ .../OidcIdTokenDecoderFactory.java | 14 +++- .../ReactiveOidcIdTokenDecoderFactory.java | 13 +++- .../client/oidc/userinfo/OidcUserService.java | 13 +++- .../registration/ClientRegistrations.java | 46 +++++++++--- .../CustomUserTypesOAuth2UserService.java | 17 +++-- .../userinfo/DefaultOAuth2UserService.java | 10 +-- ...tOAuth2UserServiceRestTemplateFactory.java | 46 ++++++++++++ .../OAuth2UserServiceRestTemplateFactory.java | 26 +++++++ .../DefaultOAuth2AuthorizedClientManager.java | 39 +++++++--- ...ReactiveOAuth2AuthorizedClientManager.java | 40 ++++++++--- ...Auth2AuthorizedClientArgumentResolver.java | 37 ++++++++-- ...uthorizedClientExchangeFilterFunction.java | 36 +++++++--- ...uthorizedClientExchangeFilterFunction.java | 16 ++++- ...Auth2AuthorizedClientArgumentResolver.java | 29 +++++++- .../JwtDecoderProviderConfigurationUtils.java | 21 ++++-- .../security/oauth2/jwt/JwtDecoders.java | 43 ++++++++--- .../security/oauth2/jwt/NimbusJwtDecoder.java | 3 +- .../oauth2/jwt/NimbusReactiveJwtDecoder.java | 3 +- .../oauth2/jwt/ReactiveJwtDecoders.java | 35 +++++++-- ...wtIssuerAuthenticationManagerResolver.java | 29 ++++++-- ...ReactiveAuthenticationManagerResolver.java | 30 ++++++-- ...eTokenIntrospectorRestTemplateFactory.java | 50 +++++++++++++ .../NimbusOpaqueTokenIntrospector.java | 25 +++++-- ...NimbusReactiveOpaqueTokenIntrospector.java | 14 +++- ...eTokenIntrospectorRestTemplateFactory.java | 26 +++++++ 44 files changed, 925 insertions(+), 197 deletions(-) create mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/OAuth2RestTemplateFactory.java create mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultOAuth2UserServiceRestTemplateFactory.java create mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/OAuth2UserServiceRestTemplateFactory.java create mode 100644 oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/DefaultOpaqueTokenIntrospectorRestTemplateFactory.java create mode 100644 oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/OpaqueTokenIntrospectorRestTemplateFactory.java diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/OAuth2ClientConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/OAuth2ClientConfiguration.java index b29212d79ef..61536b83839 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/OAuth2ClientConfiguration.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/OAuth2ClientConfiguration.java @@ -67,6 +67,9 @@ static class OAuth2ClientWebMvcSecurityConfiguration implements WebMvcConfigurer private ClientRegistrationRepository clientRegistrationRepository; private OAuth2AuthorizedClientRepository authorizedClientRepository; private OAuth2AccessTokenResponseClient accessTokenResponseClient; + private OAuth2AuthorizedClientProviderBuilder.RefreshTokenGrantBuilderCustomizer refreshTokenGrantBuilderCustomizer; + private OAuth2AuthorizedClientProviderBuilder.ClientCredentialsGrantBuilderCustomizer clientCredentialsGrantBuilderCustomizer; + private OAuth2AuthorizedClientProviderBuilder.PasswordGrantBuilderCustomizer passwordGrantBuilderCustomizer; @Override public void addArgumentResolvers(List argumentResolvers) { @@ -74,17 +77,22 @@ public void addArgumentResolvers(List argumentRes OAuth2AuthorizedClientProviderBuilder authorizedClientProviderBuilder = OAuth2AuthorizedClientProviderBuilder.builder() .authorizationCode() - .refreshToken() - .password(); + .refreshToken(refreshTokenGrantBuilderCustomizer) + .password(passwordGrantBuilderCustomizer); if (this.accessTokenResponseClient != null) { + authorizedClientProviderBuilder.clientCredentials(clientCredentialsGrantBuilderCustomizer); authorizedClientProviderBuilder.clientCredentials(configurer -> - configurer.accessTokenResponseClient(this.accessTokenResponseClient)); + configurer.accessTokenResponseClient(this.accessTokenResponseClient)); } else { - authorizedClientProviderBuilder.clientCredentials(); + authorizedClientProviderBuilder.clientCredentials(clientCredentialsGrantBuilderCustomizer); } OAuth2AuthorizedClientProvider authorizedClientProvider = authorizedClientProviderBuilder.build(); DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager( - this.clientRegistrationRepository, this.authorizedClientRepository); + this.clientRegistrationRepository, + this.authorizedClientRepository, + this.refreshTokenGrantBuilderCustomizer, + this.clientCredentialsGrantBuilderCustomizer, + this.passwordGrantBuilderCustomizer); authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); argumentResolvers.add(new OAuth2AuthorizedClientArgumentResolver(authorizedClientManager)); } @@ -104,6 +112,30 @@ public void setAuthorizedClientRepository(List } } + @Autowired(required = false) + public void setRefreshTokenGrantBuilderCustomizer( + List beans) { + if (beans.size() == 1) { + this.refreshTokenGrantBuilderCustomizer = beans.get(0); + } + } + + @Autowired(required = false) + public void setClientCredentialsGrantBuilderCustomizer( + List beans) { + if (beans.size() == 1) { + this.clientCredentialsGrantBuilderCustomizer = beans.get(0); + } + } + + @Autowired(required = false) + public void setPasswordGrantBuilderCustomizer( + List beans) { + if (beans.size() == 1) { + this.passwordGrantBuilderCustomizer = beans.get(0); + } + } + @Autowired public void setAccessTokenResponseClient( Optional> accessTokenResponseClient) { diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2ClientConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2ClientConfigurer.java index af2f56e0cd5..9b9e474e108 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2ClientConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2ClientConfigurer.java @@ -24,6 +24,7 @@ import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; +import org.springframework.security.oauth2.client.endpoint.OAuth2RestTemplateFactory; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.web.AuthenticatedPrincipalOAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository; @@ -156,6 +157,7 @@ public class AuthorizationCodeGrantConfigurer { private OAuth2AuthorizationRequestResolver authorizationRequestResolver; private AuthorizationRequestRepository authorizationRequestRepository; private OAuth2AccessTokenResponseClient accessTokenResponseClient; + private OAuth2RestTemplateFactory restTemplateFactory; private AuthorizationCodeGrantConfigurer() { } @@ -200,6 +202,12 @@ public AuthorizationCodeGrantConfigurer accessTokenResponseClient( return this; } + public AuthorizationCodeGrantConfigurer restTemplateFactory(OAuth2RestTemplateFactory restTemplateFactory) { + Assert.notNull(restTemplateFactory, "restTemplateFactory cannot be null"); + this.restTemplateFactory = restTemplateFactory; + return this; + } + /** * Returns the {@link OAuth2ClientConfigurer} for further configuration. * @@ -211,7 +219,7 @@ public OAuth2ClientConfigurer and() { private void init(B builder) { OAuth2AuthorizationCodeAuthenticationProvider authorizationCodeAuthenticationProvider = - new OAuth2AuthorizationCodeAuthenticationProvider(getAccessTokenResponseClient()); + new OAuth2AuthorizationCodeAuthenticationProvider(getOrCreateAccessTokenResponseClient()); builder.authenticationProvider(postProcess(authorizationCodeAuthenticationProvider)); } @@ -264,11 +272,14 @@ private OAuth2AuthorizationCodeGrantFilter createAuthorizationCodeGrantFilter(B return authorizationCodeGrantFilter; } - private OAuth2AccessTokenResponseClient getAccessTokenResponseClient() { - if (this.accessTokenResponseClient != null) { + private OAuth2AccessTokenResponseClient getOrCreateAccessTokenResponseClient() { + if (this.accessTokenResponseClient == null) { + return restTemplateFactory == null + ? new DefaultAuthorizationCodeTokenResponseClient() + : new DefaultAuthorizationCodeTokenResponseClient(restTemplateFactory); + } else { return this.accessTokenResponseClient; } - return new DefaultAuthorizationCodeTokenResponseClient(); } } diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java index 823de21892c..1a9aa15d9fd 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java @@ -41,6 +41,7 @@ import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; +import org.springframework.security.oauth2.client.endpoint.OAuth2RestTemplateFactory; import org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeAuthenticationProvider; import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest; import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService; @@ -48,9 +49,11 @@ import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.userinfo.CustomUserTypesOAuth2UserService; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserServiceRestTemplateFactory; import org.springframework.security.oauth2.client.userinfo.DelegatingOAuth2UserService; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserServiceRestTemplateFactory; import org.springframework.security.oauth2.client.web.AuthenticatedPrincipalOAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository; import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter; @@ -299,6 +302,7 @@ public OAuth2LoginConfigurer tokenEndpoint(Customizer to */ public class TokenEndpointConfig { private OAuth2AccessTokenResponseClient accessTokenResponseClient; + private OAuth2RestTemplateFactory restTemplateFactory; private TokenEndpointConfig() { } @@ -317,6 +321,12 @@ public TokenEndpointConfig accessTokenResponseClient( return this; } + public TokenEndpointConfig restTemplateFactory(OAuth2RestTemplateFactory restTemplateFactory) { + Assert.notNull(restTemplateFactory, "restTemplateFactory cannot be null"); + this.restTemplateFactory = restTemplateFactory; + return this; + } + /** * Returns the {@link OAuth2LoginConfigurer} for further configuration. * @@ -325,6 +335,16 @@ public TokenEndpointConfig accessTokenResponseClient( public OAuth2LoginConfigurer and() { return OAuth2LoginConfigurer.this; } + + private OAuth2AccessTokenResponseClient getOrCreateAccessTokenResponseClient() { + if (accessTokenResponseClient == null) { + return restTemplateFactory == null + ? new DefaultAuthorizationCodeTokenResponseClient() + : new DefaultAuthorizationCodeTokenResponseClient(restTemplateFactory); + } else { + return accessTokenResponseClient; + } + } } /** @@ -407,8 +427,10 @@ public class UserInfoEndpointConfig { private OAuth2UserService userService; private OAuth2UserService oidcUserService; private Map> customUserTypes = new HashMap<>(); + private OAuth2UserServiceRestTemplateFactory restTemplateFactory; private UserInfoEndpointConfig() { + this.restTemplateFactory = DefaultOAuth2UserServiceRestTemplateFactory.DEFAULT; } /** @@ -462,6 +484,12 @@ public UserInfoEndpointConfig userAuthoritiesMapper(GrantedAuthoritiesMapper use return this; } + public UserInfoEndpointConfig restTemplateFactory(OAuth2UserServiceRestTemplateFactory restTemplateFactory) { + Assert.notNull(restTemplateFactory, "restTemplateFactory cannot be null"); + this.restTemplateFactory = restTemplateFactory; + return this; + } + /** * Returns the {@link OAuth2LoginConfigurer} for further configuration. * @@ -501,10 +529,7 @@ public void init(B http) throws Exception { } OAuth2AccessTokenResponseClient accessTokenResponseClient = - this.tokenEndpointConfig.accessTokenResponseClient; - if (accessTokenResponseClient == null) { - accessTokenResponseClient = new DefaultAuthorizationCodeTokenResponseClient(); - } + this.tokenEndpointConfig.getOrCreateAccessTokenResponseClient(); OAuth2UserService oauth2UserService = getOAuth2UserService(); OAuth2LoginAuthenticationProvider oauth2LoginAuthenticationProvider = @@ -619,7 +644,7 @@ private OAuth2UserService getOidcUserService() { ResolvableType type = ResolvableType.forClassWithGenerics(OAuth2UserService.class, OidcUserRequest.class, OidcUser.class); OAuth2UserService bean = getBeanOrNull(type); if (bean == null) { - return new OidcUserService(); + return new OidcUserService(userInfoEndpointConfig.restTemplateFactory); } return bean; @@ -634,11 +659,13 @@ private OAuth2UserService getOAuth2UserService() if (bean == null) { if (!this.userInfoEndpointConfig.customUserTypes.isEmpty()) { List> userServices = new ArrayList<>(); - userServices.add(new CustomUserTypesOAuth2UserService(this.userInfoEndpointConfig.customUserTypes)); - userServices.add(new DefaultOAuth2UserService()); + userServices.add(new CustomUserTypesOAuth2UserService( + this.userInfoEndpointConfig.customUserTypes, + this.userInfoEndpointConfig.restTemplateFactory)); + userServices.add(new DefaultOAuth2UserService(this.userInfoEndpointConfig.restTemplateFactory)); return new DelegatingOAuth2UserService<>(userServices); } else { - return new DefaultOAuth2UserService(); + return new DefaultOAuth2UserService(this.userInfoEndpointConfig.restTemplateFactory); } } diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java index e28f27e2f7c..3dbacf3b639 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java @@ -40,6 +40,7 @@ import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenAuthenticationProvider; import org.springframework.security.oauth2.server.resource.introspection.NimbusOpaqueTokenIntrospector; import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector; +import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospectorRestTemplateFactory; import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint; import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter; import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver; @@ -49,6 +50,7 @@ import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; +import org.springframework.web.client.RestOperations; import static org.springframework.security.oauth2.jwt.NimbusJwtDecoder.withJwkSetUri; @@ -283,6 +285,8 @@ public class JwtConfigurer { private Converter jwtAuthenticationConverter; + private RestOperations restOperations; + JwtConfigurer(ApplicationContext context) { this.context = context; } @@ -299,7 +303,15 @@ public JwtConfigurer decoder(JwtDecoder decoder) { } public JwtConfigurer jwkSetUri(String uri) { - this.decoder = withJwkSetUri(uri).build(); + final NimbusJwtDecoder.JwkSetUriJwtDecoderBuilder builder = withJwkSetUri(uri); + this.decoder = restOperations == null + ? builder.build() + : builder.restOperations(restOperations).build(); + return this; + } + + public JwtConfigurer restOperations(RestOperations restOperations) { + this.restOperations = restOperations; return this; } @@ -366,6 +378,7 @@ public class OpaqueTokenConfigurer { private String clientId; private String clientSecret; private Supplier introspector; + private OpaqueTokenIntrospectorRestTemplateFactory restTemplateFactory; OpaqueTokenConfigurer(ApplicationContext context) { this.context = context; @@ -380,8 +393,7 @@ public OpaqueTokenConfigurer authenticationManager(AuthenticationManager authent public OpaqueTokenConfigurer introspectionUri(String introspectionUri) { Assert.notNull(introspectionUri, "introspectionUri cannot be null"); this.introspectionUri = introspectionUri; - this.introspector = () -> - new NimbusOpaqueTokenIntrospector(this.introspectionUri, this.clientId, this.clientSecret); + this.introspector = createIntrospectorSupplier(); return this; } @@ -390,11 +402,23 @@ public OpaqueTokenConfigurer introspectionClientCredentials(String clientId, Str Assert.notNull(clientSecret, "clientSecret cannot be null"); this.clientId = clientId; this.clientSecret = clientSecret; - this.introspector = () -> - new NimbusOpaqueTokenIntrospector(this.introspectionUri, this.clientId, this.clientSecret); + this.introspector = createIntrospectorSupplier(); return this; } + public OpaqueTokenConfigurer restTemplateFactory(OpaqueTokenIntrospectorRestTemplateFactory restTemplateFactory) { + Assert.notNull(restTemplateFactory, "restTemplateFactory cannot be null"); + this.restTemplateFactory = restTemplateFactory; + this.introspector = createIntrospectorSupplier(); + return this; + } + + private Supplier createIntrospectorSupplier() { + return () -> restTemplateFactory == null + ? new NimbusOpaqueTokenIntrospector(this.introspectionUri, this.clientId, this.clientSecret) + : new NimbusOpaqueTokenIntrospector(this.introspectionUri, this.clientId, this.clientSecret, restTemplateFactory); + } + public OpaqueTokenConfigurer introspector(OpaqueTokenIntrospector introspector) { Assert.notNull(introspector, "introspector cannot be null"); this.introspector = () -> introspector; diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2ClientImportSelector.java b/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2ClientImportSelector.java index 4cf69de47c2..ffc4e4173a4 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2ClientImportSelector.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2ClientImportSelector.java @@ -24,9 +24,9 @@ import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProviderBuilder; import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; +import org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2AuthorizedClientManager; import org.springframework.security.oauth2.client.web.reactive.result.method.annotation.OAuth2AuthorizedClientArgumentResolver; import org.springframework.security.oauth2.client.web.server.AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository; -import org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2AuthorizedClientManager; import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; import org.springframework.util.ClassUtils; import org.springframework.web.reactive.config.WebFluxConfigurer; @@ -63,15 +63,19 @@ static class OAuth2ClientWebFluxSecurityConfiguration implements WebFluxConfigur private ReactiveOAuth2AuthorizedClientService authorizedClientService; + private ReactiveOAuth2AuthorizedClientProviderBuilder.RefreshTokenGrantBuilderCustomizer refreshTokenGrantBuilderCustomizer; + private ReactiveOAuth2AuthorizedClientProviderBuilder.ClientCredentialsGrantBuilderCustomizer clientCredentialsGrantBuilderCustomizer; + private ReactiveOAuth2AuthorizedClientProviderBuilder.PasswordGrantBuilderCustomizer passwordGrantBuilderCustomizer; + @Override public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) { if (this.authorizedClientRepository != null && this.clientRegistrationRepository != null) { ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder() .authorizationCode() - .refreshToken() - .clientCredentials() - .password() + .refreshToken(refreshTokenGrantBuilderCustomizer) + .clientCredentials(clientCredentialsGrantBuilderCustomizer) + .password(passwordGrantBuilderCustomizer) .build(); DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager = new DefaultReactiveOAuth2AuthorizedClientManager( this.clientRegistrationRepository, getAuthorizedClientRepository()); @@ -98,6 +102,27 @@ public void setAuthorizedClientService(List beans) { + if (beans.size() == 1) { + this.refreshTokenGrantBuilderCustomizer = beans.get(0); + } + } + + @Autowired(required = false) + public void setClientCredentialsGrantBuilderCustomizer(List beans) { + if (beans.size() == 1) { + this.clientCredentialsGrantBuilderCustomizer = beans.get(0); + } + } + + @Autowired(required = false) + public void setPasswordGrantBuilderCustomizer(List beans) { + if (beans.size() == 1) { + this.passwordGrantBuilderCustomizer = beans.get(0); + } + } + private ServerOAuth2AuthorizedClientRepository getAuthorizedClientRepository() { if (this.authorizedClientRepository != null) { return this.authorizedClientRepository; diff --git a/config/src/main/java/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParser.java index 5068a74b456..466ba179f92 100644 --- a/config/src/main/java/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParser.java +++ b/config/src/main/java/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParser.java @@ -24,6 +24,7 @@ import org.springframework.beans.BeanMetadataElement; import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanReference; import org.springframework.beans.factory.config.RuntimeBeanReference; @@ -50,6 +51,7 @@ import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; +import org.springframework.web.client.RestOperations; /** * A {@link BeanDefinitionParser} for <http>'s <oauth2-resource-server> element. @@ -322,6 +324,7 @@ public AuthenticationManager resolve(HttpServletRequest context) { final class NimbusJwtDecoderJwkSetUriFactoryBean implements FactoryBean { private final String jwkSetUri; + private RestOperations restOperations; NimbusJwtDecoderJwkSetUriFactoryBean(String jwkSetUri) { this.jwkSetUri = jwkSetUri; @@ -329,13 +332,21 @@ final class NimbusJwtDecoderJwkSetUriFactoryBean implements FactoryBean getObjectType() { return JwtDecoder.class; } + + @Autowired(required = false) + public void setRestOperations(RestOperations restOperations) { + this.restOperations = restOperations; + } } final class BearerTokenRequestMatcher implements RequestMatcher { diff --git a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java index 7f2275c9a60..84f0fa08830 100644 --- a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java @@ -32,6 +32,7 @@ import java.util.function.Supplier; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; +import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; import reactor.util.context.Context; @@ -1081,6 +1082,15 @@ private ReactiveAuthenticationManager createDefault() { return oauth2Manager; } + private ReactiveOAuth2AccessTokenResponseClient getAccessTokenResponseClient() { + ResolvableType type = ResolvableType.forClassWithGenerics(ReactiveOAuth2AccessTokenResponseClient.class, OAuth2AuthorizationCodeGrantRequest.class); + ReactiveOAuth2AccessTokenResponseClient bean = getBeanOrNull(type); + if (bean == null) { + return new WebClientReactiveAuthorizationCodeTokenResponseClient(); + } + return bean; + } + /** * Sets the converter to use * @param authenticationConverter the converter to use @@ -1287,15 +1297,6 @@ private Map getLinks() { return result; } - private ReactiveOAuth2AccessTokenResponseClient getAccessTokenResponseClient() { - ResolvableType type = ResolvableType.forClassWithGenerics(ReactiveOAuth2AccessTokenResponseClient.class, OAuth2AuthorizationCodeGrantRequest.class); - ReactiveOAuth2AccessTokenResponseClient bean = getBeanOrNull(type); - if (bean == null) { - return new WebClientReactiveAuthorizationCodeTokenResponseClient(); - } - return bean; - } - private ReactiveClientRegistrationRepository getClientRegistrationRepository() { if (this.clientRegistrationRepository == null) { this.clientRegistrationRepository = getBeanOrNull(ReactiveClientRegistrationRepository.class); @@ -1450,11 +1451,20 @@ public OAuth2ClientSpec authenticationManager(ReactiveAuthenticationManager auth */ private ReactiveAuthenticationManager getAuthenticationManager() { if (this.authenticationManager == null) { - this.authenticationManager = new OAuth2AuthorizationCodeReactiveAuthenticationManager(new WebClientReactiveAuthorizationCodeTokenResponseClient()); + this.authenticationManager = new OAuth2AuthorizationCodeReactiveAuthenticationManager(getAccessTokenResponseClient()); } return this.authenticationManager; } + private ReactiveOAuth2AccessTokenResponseClient getAccessTokenResponseClient() { + ResolvableType type = ResolvableType.forClassWithGenerics(ReactiveOAuth2AccessTokenResponseClient.class, OAuth2AuthorizationCodeGrantRequest.class); + ReactiveOAuth2AccessTokenResponseClient bean = getBeanOrNull(type); + if (bean == null) { + return new WebClientReactiveAuthorizationCodeTokenResponseClient(); + } + return bean; + } + /** * Configures the {@link ReactiveClientRegistrationRepository}. Default is to look the value up as a Bean. * @param clientRegistrationRepository the repository to use @@ -1971,6 +1981,16 @@ public class OpaqueTokenSpec { private String clientId; private String clientSecret; private Supplier introspector; + private WebClient webClient; + + public OpaqueTokenSpec webClient(WebClient webClient) { + Assert.notNull(webClient, "webClient cannot be null"); + this.webClient = webClient; + this.introspector = () -> + new NimbusReactiveOpaqueTokenIntrospector( + this.introspectionUri, this.clientId, this.clientSecret, webClient); + return this; + } /** * Configures the URI of the Introspection endpoint @@ -1982,7 +2002,7 @@ public OpaqueTokenSpec introspectionUri(String introspectionUri) { this.introspectionUri = introspectionUri; this.introspector = () -> new NimbusReactiveOpaqueTokenIntrospector( - this.introspectionUri, this.clientId, this.clientSecret); + this.introspectionUri, this.clientId, this.clientSecret, webClient); return this; } @@ -1999,7 +2019,7 @@ public OpaqueTokenSpec introspectionClientCredentials(String clientId, String cl this.clientSecret = clientSecret; this.introspector = () -> new NimbusReactiveOpaqueTokenIntrospector( - this.introspectionUri, this.clientId, this.clientSecret); + this.introspectionUri, this.clientId, this.clientSecret, webClient); return this; } diff --git a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerOpaqueTokenDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerOpaqueTokenDsl.kt index 72d9bf103fb..a27de759981 100644 --- a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerOpaqueTokenDsl.kt +++ b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerOpaqueTokenDsl.kt @@ -16,7 +16,9 @@ package org.springframework.security.config.web.server +import org.springframework.security.config.web.servlet.oauth2.resourceserver.OAuth2ResourceServerSecurityMarker import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector +import org.springframework.web.reactive.function.client.WebClient /** * A Kotlin DSL to configure [ServerHttpSecurity] Opaque Token Resource Server support using idiomatic Kotlin code. @@ -30,6 +32,7 @@ import org.springframework.security.oauth2.server.resource.introspection.Reactiv class ServerOpaqueTokenDsl { private var _introspectionUri: String? = null private var _introspector: ReactiveOpaqueTokenIntrospector? = null + private var _webClient: WebClient? = null private var clientCredentials: Pair? = null var introspectionUri: String? @@ -38,6 +41,7 @@ class ServerOpaqueTokenDsl { _introspectionUri = value _introspector = null } + var introspector: ReactiveOpaqueTokenIntrospector? get() = _introspector set(value) { @@ -46,6 +50,12 @@ class ServerOpaqueTokenDsl { clientCredentials = null } + var webClient: WebClient? + get() = _webClient + set(value) { + _webClient = value + } + /** * Configures the credentials for Introspection endpoint. * @@ -59,6 +69,7 @@ class ServerOpaqueTokenDsl { internal fun get(): (ServerHttpSecurity.OAuth2ResourceServerSpec.OpaqueTokenSpec) -> Unit { return { opaqueToken -> + webClient?.also { opaqueToken.webClient(webClient) } introspectionUri?.also { opaqueToken.introspectionUri(introspectionUri) } clientCredentials?.also { opaqueToken.introspectionClientCredentials(clientCredentials!!.first, clientCredentials!!.second) } introspector?.also { opaqueToken.introspector(introspector) } diff --git a/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/login/UserInfoEndpointDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/login/UserInfoEndpointDsl.kt index edddc41d799..a2860091f6f 100644 --- a/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/login/UserInfoEndpointDsl.kt +++ b/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/login/UserInfoEndpointDsl.kt @@ -23,6 +23,7 @@ import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest import org.springframework.security.oauth2.client.registration.ClientRegistration import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest import org.springframework.security.oauth2.client.userinfo.OAuth2UserService +import org.springframework.security.oauth2.client.userinfo.OAuth2UserServiceRestTemplateFactory import org.springframework.security.oauth2.core.oidc.user.OidcUser import org.springframework.security.oauth2.core.user.OAuth2User @@ -37,12 +38,14 @@ import org.springframework.security.oauth2.core.user.OAuth2User * @property oidcUserService the OpenID Connect 1.0 service used for obtaining the user attributes of the * End-User from the UserInfo Endpoint. * @property userAuthoritiesMapper the [GrantedAuthoritiesMapper] used for mapping [OAuth2User.getAuthorities] + * @property oAuth2UserServiceRestTemplateFactory the [OAuth2UserServiceRestTemplateFactory] used for creating RestTemplate */ @OAuth2LoginSecurityMarker class UserInfoEndpointDsl { var userService: OAuth2UserService? = null var oidcUserService: OAuth2UserService? = null var userAuthoritiesMapper: GrantedAuthoritiesMapper? = null + var oAuth2UserServiceRestTemplateFactory: OAuth2UserServiceRestTemplateFactory? = null private var customUserTypePair: Pair, String>? = null @@ -62,6 +65,7 @@ class UserInfoEndpointDsl { userService?.also { userInfoEndpoint.userService(userService) } oidcUserService?.also { userInfoEndpoint.oidcUserService(oidcUserService) } userAuthoritiesMapper?.also { userInfoEndpoint.userAuthoritiesMapper(userAuthoritiesMapper) } + oAuth2UserServiceRestTemplateFactory?.also { userInfoEndpoint.restTemplateFactory(oAuth2UserServiceRestTemplateFactory) } customUserTypePair?.also { userInfoEndpoint.customUserType(customUserTypePair!!.first, customUserTypePair!!.second) } } } diff --git a/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/resourceserver/JwtDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/resourceserver/JwtDsl.kt index e8d8008a974..038e34cbeee 100644 --- a/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/resourceserver/JwtDsl.kt +++ b/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/resourceserver/JwtDsl.kt @@ -22,6 +22,7 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer import org.springframework.security.oauth2.jwt.Jwt import org.springframework.security.oauth2.jwt.JwtDecoder +import org.springframework.web.client.RestOperations /** * A Kotlin DSL to configure JWT Resource Server Support using idiomatic Kotlin code. @@ -38,6 +39,7 @@ import org.springframework.security.oauth2.jwt.JwtDecoder class JwtDsl { private var _jwtDecoder: JwtDecoder? = null private var _jwkSetUri: String? = null + private var _restOperations: RestOperations? = null var jwtAuthenticationConverter: Converter? = null var jwtDecoder: JwtDecoder? @@ -53,10 +55,17 @@ class JwtDsl { _jwtDecoder = null } + var restOperations: RestOperations? + get() = _restOperations + set(value) { + _restOperations = value + } + internal fun get(): (OAuth2ResourceServerConfigurer.JwtConfigurer) -> Unit { return { jwt -> jwtAuthenticationConverter?.also { jwt.jwtAuthenticationConverter(jwtAuthenticationConverter) } jwtDecoder?.also { jwt.decoder(jwtDecoder) } + restOperations?.also { jwt.restOperations(restOperations) } jwkSetUri?.also { jwt.jwkSetUri(jwkSetUri) } } } diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index ff8f81bacc9..22c2e132214 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -35,7 +35,7 @@ dependencies { management "com.google.appengine:appengine-testing:$gaeVersion" management "com.google.appengine:appengine:$gaeVersion" management "com.google.inject:guice:3.0" - management "com.nimbusds:nimbus-jose-jwt:latest.release" + management "com.nimbusds:nimbus-jose-jwt:8.17.1" management "com.nimbusds:oauth2-oidc-sdk:latest.release" management "com.squareup.okhttp3:mockwebserver:3.+" management "com.squareup.okhttp3:okhttp:3.+" diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientProviderBuilder.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientProviderBuilder.java index 7d02a4efcc6..49599d87fb4 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientProviderBuilder.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientProviderBuilder.java @@ -110,20 +110,22 @@ public OAuth2AuthorizedClientProvider build() { * @return the {@link OAuth2AuthorizedClientProviderBuilder} */ public OAuth2AuthorizedClientProviderBuilder refreshToken() { - this.builders.computeIfAbsent(RefreshTokenOAuth2AuthorizedClientProvider.class, k -> new RefreshTokenGrantBuilder()); + refreshToken(null); return OAuth2AuthorizedClientProviderBuilder.this; } /** * Configures support for the {@code refresh_token} grant. * - * @param builderConsumer a {@code Consumer} of {@link RefreshTokenGrantBuilder} used for further configuration + * @param customizer of {@link RefreshTokenGrantBuilder} used for further configuration * @return the {@link OAuth2AuthorizedClientProviderBuilder} */ - public OAuth2AuthorizedClientProviderBuilder refreshToken(Consumer builderConsumer) { + public OAuth2AuthorizedClientProviderBuilder refreshToken(RefreshTokenGrantBuilderCustomizer customizer) { RefreshTokenGrantBuilder builder = (RefreshTokenGrantBuilder) this.builders.computeIfAbsent( RefreshTokenOAuth2AuthorizedClientProvider.class, k -> new RefreshTokenGrantBuilder()); - builderConsumer.accept(builder); + if (customizer != null) { + customizer.accept(builder); + } return OAuth2AuthorizedClientProviderBuilder.this; } @@ -199,20 +201,22 @@ public OAuth2AuthorizedClientProvider build() { * @return the {@link OAuth2AuthorizedClientProviderBuilder} */ public OAuth2AuthorizedClientProviderBuilder clientCredentials() { - this.builders.computeIfAbsent(ClientCredentialsOAuth2AuthorizedClientProvider.class, k -> new ClientCredentialsGrantBuilder()); + clientCredentials(null); return OAuth2AuthorizedClientProviderBuilder.this; } /** * Configures support for the {@code client_credentials} grant. * - * @param builderConsumer a {@code Consumer} of {@link ClientCredentialsGrantBuilder} used for further configuration + * @param customizer of {@link ClientCredentialsGrantBuilder} used for further configuration * @return the {@link OAuth2AuthorizedClientProviderBuilder} */ - public OAuth2AuthorizedClientProviderBuilder clientCredentials(Consumer builderConsumer) { + public OAuth2AuthorizedClientProviderBuilder clientCredentials(ClientCredentialsGrantBuilderCustomizer customizer) { ClientCredentialsGrantBuilder builder = (ClientCredentialsGrantBuilder) this.builders.computeIfAbsent( ClientCredentialsOAuth2AuthorizedClientProvider.class, k -> new ClientCredentialsGrantBuilder()); - builderConsumer.accept(builder); + if (customizer != null) { + customizer.accept(builder); + } return OAuth2AuthorizedClientProviderBuilder.this; } @@ -288,20 +292,22 @@ public OAuth2AuthorizedClientProvider build() { * @return the {@link OAuth2AuthorizedClientProviderBuilder} */ public OAuth2AuthorizedClientProviderBuilder password() { - this.builders.computeIfAbsent(PasswordOAuth2AuthorizedClientProvider.class, k -> new PasswordGrantBuilder()); + password(null); return OAuth2AuthorizedClientProviderBuilder.this; } /** * Configures support for the {@code password} grant. * - * @param builderConsumer a {@code Consumer} of {@link PasswordGrantBuilder} used for further configuration + * @param customizer of {@link PasswordGrantBuilder} used for further configuration * @return the {@link OAuth2AuthorizedClientProviderBuilder} */ - public OAuth2AuthorizedClientProviderBuilder password(Consumer builderConsumer) { + public OAuth2AuthorizedClientProviderBuilder password(PasswordGrantBuilderCustomizer customizer) { PasswordGrantBuilder builder = (PasswordGrantBuilder) this.builders.computeIfAbsent( PasswordOAuth2AuthorizedClientProvider.class, k -> new PasswordGrantBuilder()); - builderConsumer.accept(builder); + if (customizer != null) { + customizer.accept(builder); + } return OAuth2AuthorizedClientProviderBuilder.this; } @@ -388,4 +394,13 @@ public OAuth2AuthorizedClientProvider build() { interface Builder { OAuth2AuthorizedClientProvider build(); } + + public interface RefreshTokenGrantBuilderCustomizer extends Consumer { + } + + public interface ClientCredentialsGrantBuilderCustomizer extends Consumer { + } + + public interface PasswordGrantBuilderCustomizer extends Consumer { + } } diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ReactiveOAuth2AuthorizedClientProviderBuilder.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ReactiveOAuth2AuthorizedClientProviderBuilder.java index 482b5962ecc..53a732cc8c8 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ReactiveOAuth2AuthorizedClientProviderBuilder.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ReactiveOAuth2AuthorizedClientProviderBuilder.java @@ -110,20 +110,21 @@ public ReactiveOAuth2AuthorizedClientProvider build() { * @return the {@link ReactiveOAuth2AuthorizedClientProviderBuilder} */ public ReactiveOAuth2AuthorizedClientProviderBuilder refreshToken() { - this.builders.computeIfAbsent(RefreshTokenReactiveOAuth2AuthorizedClientProvider.class, k -> new RefreshTokenGrantBuilder()); - return ReactiveOAuth2AuthorizedClientProviderBuilder.this; + return refreshToken(null); } /** * Configures support for the {@code refresh_token} grant. * - * @param builderConsumer a {@code Consumer} of {@link RefreshTokenGrantBuilder} used for further configuration + * @param customizer of {@link RefreshTokenGrantBuilder} used for further configuration * @return the {@link ReactiveOAuth2AuthorizedClientProviderBuilder} */ - public ReactiveOAuth2AuthorizedClientProviderBuilder refreshToken(Consumer builderConsumer) { + public ReactiveOAuth2AuthorizedClientProviderBuilder refreshToken(RefreshTokenGrantBuilderCustomizer customizer) { RefreshTokenGrantBuilder builder = (RefreshTokenGrantBuilder) this.builders.computeIfAbsent( RefreshTokenReactiveOAuth2AuthorizedClientProvider.class, k -> new RefreshTokenGrantBuilder()); - builderConsumer.accept(builder); + if (customizer != null) { + customizer.accept(builder); + } return ReactiveOAuth2AuthorizedClientProviderBuilder.this; } @@ -199,20 +200,21 @@ public ReactiveOAuth2AuthorizedClientProvider build() { * @return the {@link ReactiveOAuth2AuthorizedClientProviderBuilder} */ public ReactiveOAuth2AuthorizedClientProviderBuilder clientCredentials() { - this.builders.computeIfAbsent(ClientCredentialsReactiveOAuth2AuthorizedClientProvider.class, k -> new ClientCredentialsGrantBuilder()); - return ReactiveOAuth2AuthorizedClientProviderBuilder.this; + return clientCredentials(null); } /** * Configures support for the {@code client_credentials} grant. * - * @param builderConsumer a {@code Consumer} of {@link ClientCredentialsGrantBuilder} used for further configuration + * @param customizer of {@link ClientCredentialsGrantBuilder} used for further configuration * @return the {@link ReactiveOAuth2AuthorizedClientProviderBuilder} */ - public ReactiveOAuth2AuthorizedClientProviderBuilder clientCredentials(Consumer builderConsumer) { + public ReactiveOAuth2AuthorizedClientProviderBuilder clientCredentials(ClientCredentialsGrantBuilderCustomizer customizer) { ClientCredentialsGrantBuilder builder = (ClientCredentialsGrantBuilder) this.builders.computeIfAbsent( ClientCredentialsReactiveOAuth2AuthorizedClientProvider.class, k -> new ClientCredentialsGrantBuilder()); - builderConsumer.accept(builder); + if (customizer != null) { + customizer.accept(builder); + } return ReactiveOAuth2AuthorizedClientProviderBuilder.this; } @@ -288,20 +290,21 @@ public ReactiveOAuth2AuthorizedClientProvider build() { * @return the {@link ReactiveOAuth2AuthorizedClientProviderBuilder} */ public ReactiveOAuth2AuthorizedClientProviderBuilder password() { - this.builders.computeIfAbsent(PasswordReactiveOAuth2AuthorizedClientProvider.class, k -> new PasswordGrantBuilder()); - return ReactiveOAuth2AuthorizedClientProviderBuilder.this; + return password(null); } /** * Configures support for the {@code password} grant. * - * @param builderConsumer a {@code Consumer} of {@link PasswordGrantBuilder} used for further configuration + * @param customizer of {@link PasswordGrantBuilder} used for further configuration * @return the {@link ReactiveOAuth2AuthorizedClientProviderBuilder} */ - public ReactiveOAuth2AuthorizedClientProviderBuilder password(Consumer builderConsumer) { + public ReactiveOAuth2AuthorizedClientProviderBuilder password(PasswordGrantBuilderCustomizer customizer) { PasswordGrantBuilder builder = (PasswordGrantBuilder) this.builders.computeIfAbsent( PasswordReactiveOAuth2AuthorizedClientProvider.class, k -> new PasswordGrantBuilder()); - builderConsumer.accept(builder); + if (customizer != null) { + customizer.accept(builder); + } return ReactiveOAuth2AuthorizedClientProviderBuilder.this; } @@ -388,4 +391,13 @@ public ReactiveOAuth2AuthorizedClientProvider build() { interface Builder { ReactiveOAuth2AuthorizedClientProvider build(); } + + public interface RefreshTokenGrantBuilderCustomizer extends Consumer { + } + + public interface ClientCredentialsGrantBuilderCustomizer extends Consumer { + } + + public interface PasswordGrantBuilderCustomizer extends Consumer { + } } diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/AbstractWebClientReactiveOAuth2AccessTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/AbstractWebClientReactiveOAuth2AccessTokenResponseClient.java index 2991b855a8d..bbb5ee88336 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/AbstractWebClientReactiveOAuth2AccessTokenResponseClient.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/AbstractWebClientReactiveOAuth2AccessTokenResponseClient.java @@ -54,7 +54,7 @@ abstract class AbstractWebClientReactiveOAuth2AccessTokenResponseClient implements ReactiveOAuth2AccessTokenResponseClient { - private WebClient webClient = WebClient.builder().build(); + private WebClient webClient = WebClient.create(); @Override public Mono getTokenResponse(T grantRequest) { diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultAuthorizationCodeTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultAuthorizationCodeTokenResponseClient.java index 174dc75e430..88ab4b2dc9b 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultAuthorizationCodeTokenResponseClient.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultAuthorizationCodeTokenResponseClient.java @@ -31,9 +31,6 @@ import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestOperations; -import org.springframework.web.client.RestTemplate; - -import java.util.Arrays; /** * The default implementation of an {@link OAuth2AccessTokenResponseClient} @@ -57,11 +54,13 @@ public final class DefaultAuthorizationCodeTokenResponseClient implements OAuth2 private RestOperations restOperations; + public DefaultAuthorizationCodeTokenResponseClient(OAuth2RestTemplateFactory restTemplateFactory) { + Assert.notNull(restTemplateFactory, "restTemplateFactory cannot be null"); + this.restOperations = restTemplateFactory.create(); + } + public DefaultAuthorizationCodeTokenResponseClient() { - RestTemplate restTemplate = new RestTemplate(Arrays.asList( - new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter())); - restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler()); - this.restOperations = restTemplate; + this(OAuth2RestTemplateFactory.DEFAULT); } @Override diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultClientCredentialsTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultClientCredentialsTokenResponseClient.java index fdd5eb1e75d..737d50578db 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultClientCredentialsTokenResponseClient.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultClientCredentialsTokenResponseClient.java @@ -31,9 +31,6 @@ import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestOperations; -import org.springframework.web.client.RestTemplate; - -import java.util.Arrays; /** * The default implementation of an {@link OAuth2AccessTokenResponseClient} @@ -57,11 +54,13 @@ public final class DefaultClientCredentialsTokenResponseClient implements OAuth2 private RestOperations restOperations; + public DefaultClientCredentialsTokenResponseClient(OAuth2RestTemplateFactory restTemplateFactory) { + Assert.notNull(restTemplateFactory, "restTemplateFactory cannot be null"); + this.restOperations = restTemplateFactory.create(); + } + public DefaultClientCredentialsTokenResponseClient() { - RestTemplate restTemplate = new RestTemplate(Arrays.asList( - new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter())); - restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler()); - this.restOperations = restTemplate; + this(OAuth2RestTemplateFactory.DEFAULT); } @Override diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultPasswordTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultPasswordTokenResponseClient.java index e2f7180d2e8..8ef84556391 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultPasswordTokenResponseClient.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultPasswordTokenResponseClient.java @@ -31,9 +31,6 @@ import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestOperations; -import org.springframework.web.client.RestTemplate; - -import java.util.Arrays; /** * The default implementation of an {@link OAuth2AccessTokenResponseClient} @@ -57,11 +54,13 @@ public final class DefaultPasswordTokenResponseClient implements OAuth2AccessTok private RestOperations restOperations; + public DefaultPasswordTokenResponseClient(OAuth2RestTemplateFactory restTemplateFactory) { + Assert.notNull(restTemplateFactory, "restTemplateFactory cannot be null"); + this.restOperations = restTemplateFactory.create(); + } + public DefaultPasswordTokenResponseClient() { - RestTemplate restTemplate = new RestTemplate(Arrays.asList( - new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter())); - restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler()); - this.restOperations = restTemplate; + this(OAuth2RestTemplateFactory.DEFAULT); } @Override diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultRefreshTokenTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultRefreshTokenTokenResponseClient.java index 0efd37d8ebd..63d8ddfd85c 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultRefreshTokenTokenResponseClient.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultRefreshTokenTokenResponseClient.java @@ -31,9 +31,6 @@ import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestOperations; -import org.springframework.web.client.RestTemplate; - -import java.util.Arrays; /** * The default implementation of an {@link OAuth2AccessTokenResponseClient} @@ -56,11 +53,13 @@ public final class DefaultRefreshTokenTokenResponseClient implements OAuth2Acces private RestOperations restOperations; + public DefaultRefreshTokenTokenResponseClient(OAuth2RestTemplateFactory restTemplateFactory) { + Assert.notNull(restTemplateFactory, "restTemplateFactory cannot be null"); + this.restOperations = restTemplateFactory.create(); + } + public DefaultRefreshTokenTokenResponseClient() { - RestTemplate restTemplate = new RestTemplate(Arrays.asList( - new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter())); - restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler()); - this.restOperations = restTemplate; + this(OAuth2RestTemplateFactory.DEFAULT); } @Override diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/OAuth2RestTemplateFactory.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/OAuth2RestTemplateFactory.java new file mode 100644 index 00000000000..2d4e703879b --- /dev/null +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/OAuth2RestTemplateFactory.java @@ -0,0 +1,72 @@ +/* + * Copyright 2002-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.oauth2.client.endpoint; + +import org.springframework.http.converter.FormHttpMessageConverter; +import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler; +import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; +import org.springframework.web.client.RestTemplate; + +import java.util.Arrays; + +/** + * Factory for creating {@link RestTemplate} used by various OAuth2 classes. + * + * @since 5.3 + */ +public class OAuth2RestTemplateFactory { + public static final OAuth2RestTemplateFactory DEFAULT = new OAuth2RestTemplateFactory(); + + private final Customizer customizer; + + public OAuth2RestTemplateFactory() { + this(null); + } + + public OAuth2RestTemplateFactory(Customizer customizer) { + this.customizer = customizer; + } + + /** + * Create OAuth2 {@link RestTemplate} + */ + public RestTemplate create() { + RestTemplate restTemplate = new RestTemplate(Arrays.asList( + new FormHttpMessageConverter(), + new OAuth2AccessTokenResponseHttpMessageConverter())); + restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler()); + if (customizer != null) { + customizer.customize(restTemplate); + } + return restTemplate; + + } + + /** + * Callback interface that can be used to customize a {@link RestTemplate}. + * + * @since 5.3 + */ + @FunctionalInterface + interface Customizer { + /** + * Callback to customize a {@link RestTemplate} instance. + * + * @param restTemplate the template to customize + */ + void customize(RestTemplate restTemplate); + } +} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcIdTokenDecoderFactory.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcIdTokenDecoderFactory.java index da51f71d629..5be5b5fa12e 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcIdTokenDecoderFactory.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcIdTokenDecoderFactory.java @@ -46,6 +46,7 @@ import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; import org.springframework.util.Assert; import org.springframework.util.StringUtils; +import org.springframework.web.client.RestOperations; import static org.springframework.security.oauth2.jwt.NimbusJwtDecoder.withJwkSetUri; import static org.springframework.security.oauth2.jwt.NimbusJwtDecoder.withSecretKey; @@ -79,6 +80,7 @@ public final class OidcIdTokenDecoderFactory implements JwtDecoderFactory jwsAlgorithmResolver = clientRegistration -> SignatureAlgorithm.RS256; private Function, Map>> claimTypeConverterFactory = clientRegistration -> DEFAULT_CLAIM_TYPE_CONVERTER; + private RestOperations restOperations; /** * Returns the default {@link Converter}'s used for type conversion of claim values for an {@link OidcIdToken}. @@ -153,7 +155,12 @@ private NimbusJwtDecoder buildDecoder(ClientRegistration clientRegistration) { ); throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); } - return withJwkSetUri(jwkSetUri).jwsAlgorithm((SignatureAlgorithm) jwsAlgorithm).build(); + final NimbusJwtDecoder.JwkSetUriJwtDecoderBuilder builder = withJwkSetUri(jwkSetUri) + .jwsAlgorithm((SignatureAlgorithm) jwsAlgorithm); + if (restOperations != null) { + builder.restOperations(restOperations); + } + return builder.build(); } else if (jwsAlgorithm != null && MacAlgorithm.class.isAssignableFrom(jwsAlgorithm.getClass())) { // https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation // @@ -226,4 +233,9 @@ public void setClaimTypeConverterFactory(Function jwsAlgorithmResolver = clientRegistration -> SignatureAlgorithm.RS256; private Function, Map>> claimTypeConverterFactory = clientRegistration -> DEFAULT_CLAIM_TYPE_CONVERTER; + private WebClient webClient; /** * Returns the default {@link Converter}'s used for type conversion of claim values for an {@link OidcIdToken}. @@ -153,7 +155,12 @@ private NimbusReactiveJwtDecoder buildDecoder(ClientRegistration clientRegistrat ); throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); } - return withJwkSetUri(jwkSetUri).jwsAlgorithm((SignatureAlgorithm) jwsAlgorithm).build(); + final NimbusReactiveJwtDecoder.JwkSetUriReactiveJwtDecoderBuilder builder = withJwkSetUri(jwkSetUri) + .jwsAlgorithm((SignatureAlgorithm) jwsAlgorithm); + if (webClient != null) { + builder.webClient(webClient); + } + return builder.build(); } else if (jwsAlgorithm != null && MacAlgorithm.class.isAssignableFrom(jwsAlgorithm.getClass())) { // https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation // @@ -226,4 +233,8 @@ public void setClaimTypeConverterFactory(Function accessibleScopes = new HashSet<>(Arrays.asList( OidcScopes.PROFILE, OidcScopes.EMAIL, OidcScopes.ADDRESS, OidcScopes.PHONE)); - private OAuth2UserService oauth2UserService = new DefaultOAuth2UserService(); + private OAuth2UserService oauth2UserService; private Function, Map>> claimTypeConverterFactory = clientRegistration -> DEFAULT_CLAIM_TYPE_CONVERTER; @@ -92,6 +94,15 @@ public class OidcUserService implements OAuth2UserService ClaimConversionService.getSharedInstance().convert(source, sourceDescriptor, targetDescriptor); } + public OidcUserService() { + this(DefaultOAuth2UserServiceRestTemplateFactory.DEFAULT); + } + + public OidcUserService(OAuth2UserServiceRestTemplateFactory restTemplateFactory) { + Assert.notNull(restTemplateFactory, "restTemplateFactory cannot be null"); + this.oauth2UserService = new DefaultOAuth2UserService(restTemplateFactory); + } + @Override public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException { Assert.notNull(userRequest, "userRequest cannot be null"); diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistrations.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistrations.java index 857b150db09..3799bd42429 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistrations.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistrations.java @@ -38,6 +38,7 @@ import org.springframework.security.oauth2.core.oidc.OidcScopes; import org.springframework.util.Assert; import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestOperations; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; @@ -55,7 +56,7 @@ public final class ClientRegistrations { private static final String OIDC_METADATA_PATH = "/.well-known/openid-configuration"; private static final String OAUTH_METADATA_PATH = "/.well-known/oauth-authorization-server"; - private static final RestTemplate rest = new RestTemplate(); + private static final RestTemplate DEFAULT_REST = new RestTemplate(); private static final ParameterizedTypeReference> typeReference = new ParameterizedTypeReference>() {}; @@ -86,8 +87,18 @@ public final class ClientRegistrations { * @return a {@link ClientRegistration.Builder} that was initialized by the OpenID Provider Configuration. */ public static ClientRegistration.Builder fromOidcIssuerLocation(String issuer) { + return fromOidcIssuerLocation(issuer, DEFAULT_REST); + } + + /** + * Variant of {@link #fromOidcIssuerLocation(String)} with customizable {@link RestOperations}. + * + * @since 5.3 + */ + public static ClientRegistration.Builder fromOidcIssuerLocation(String issuer, RestOperations restOperations) { Assert.hasText(issuer, "issuer cannot be empty"); - return getBuilder(issuer, oidc(URI.create(issuer))); + Assert.notNull(restOperations, "restOperations cannot be null"); + return getBuilder(issuer, oidc(URI.create(issuer), restOperations)); } /** @@ -133,18 +144,31 @@ public static ClientRegistration.Builder fromOidcIssuerLocation(String issuer) { * @return a {@link ClientRegistration.Builder} that was initialized by one of the described endpoints */ public static ClientRegistration.Builder fromIssuerLocation(String issuer) { + return fromIssuerLocation(issuer, DEFAULT_REST); + } + + /** + * Variant of {@link #fromIssuerLocation(String)} with customizable {@link RestOperations}. + * + * @since 5.3 + */ + public static ClientRegistration.Builder fromIssuerLocation(String issuer, RestOperations restOperations) { Assert.hasText(issuer, "issuer cannot be empty"); + Assert.notNull(restOperations, "restOperations cannot be null"); URI uri = URI.create(issuer); - return getBuilder(issuer, oidc(uri), oidcRfc8414(uri), oauth(uri)); + return getBuilder(issuer, + oidc(uri, restOperations), + oidcRfc8414(uri, restOperations), + oauth(uri, restOperations)); } - private static Supplier oidc(URI issuer) { + private static Supplier oidc(URI issuer, RestOperations restOperations) { URI uri = UriComponentsBuilder.fromUri(issuer) .replacePath(issuer.getPath() + OIDC_METADATA_PATH).build(Collections.emptyMap()); return () -> { RequestEntity request = RequestEntity.get(uri).build(); - Map configuration = rest.exchange(request, typeReference).getBody(); + Map configuration = restOperations.exchange(request, typeReference).getBody(); OIDCProviderMetadata metadata = parse(configuration, OIDCProviderMetadata::parse); ClientRegistration.Builder builder = withProviderConfiguration(metadata, issuer.toASCIIString()) .jwkSetUri(metadata.getJWKSetURI().toASCIIString()); @@ -155,22 +179,22 @@ private static Supplier oidc(URI issuer) { }; } - private static Supplier oidcRfc8414(URI issuer) { + private static Supplier oidcRfc8414(URI issuer, RestOperations restOperations) { URI uri = UriComponentsBuilder.fromUri(issuer) .replacePath(OIDC_METADATA_PATH + issuer.getPath()).build(Collections.emptyMap()); - return getRfc8414Builder(issuer, uri); + return getRfc8414Builder(issuer, uri, restOperations); } - private static Supplier oauth(URI issuer) { + private static Supplier oauth(URI issuer, RestOperations restOperations) { URI uri = UriComponentsBuilder.fromUri(issuer) .replacePath(OAUTH_METADATA_PATH + issuer.getPath()).build(Collections.emptyMap()); - return getRfc8414Builder(issuer, uri); + return getRfc8414Builder(issuer, uri, restOperations); } - private static Supplier getRfc8414Builder(URI issuer, URI uri) { + private static Supplier getRfc8414Builder(URI issuer, URI uri, RestOperations restOperations) { return () -> { RequestEntity request = RequestEntity.get(uri).build(); - Map configuration = rest.exchange(request, typeReference).getBody(); + Map configuration = restOperations.exchange(request, typeReference).getBody(); AuthorizationServerMetadata metadata = parse(configuration, AuthorizationServerMetadata::parse); ClientRegistration.Builder builder = withProviderConfiguration(metadata, issuer.toASCIIString()); diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/CustomUserTypesOAuth2UserService.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/CustomUserTypesOAuth2UserService.java index 95628faf4bd..8ab13c9f14c 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/CustomUserTypesOAuth2UserService.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/CustomUserTypesOAuth2UserService.java @@ -27,7 +27,6 @@ import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestOperations; -import org.springframework.web.client.RestTemplate; import java.util.Collections; import java.util.LinkedHashMap; @@ -62,11 +61,21 @@ public class CustomUserTypesOAuth2UserService implements OAuth2UserService> customUserTypes) { + this(customUserTypes, DefaultOAuth2UserServiceRestTemplateFactory.DEFAULT); + } + + /** + * Variant of {@link #CustomUserTypesOAuth2UserService(Map)} with customizable {@link OAuth2UserServiceRestTemplateFactory}. + * + * @param restTemplateFactory for creating {@link org.springframework.web.client.RestTemplate} + * @since 5.3 + */ + public CustomUserTypesOAuth2UserService(Map> customUserTypes, + OAuth2UserServiceRestTemplateFactory restTemplateFactory) { Assert.notEmpty(customUserTypes, "customUserTypes cannot be empty"); + Assert.notNull(restTemplateFactory, "restTemplateFactory cannot be null"); this.customUserTypes = Collections.unmodifiableMap(new LinkedHashMap<>(customUserTypes)); - RestTemplate restTemplate = new RestTemplate(); - restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler()); - this.restOperations = restTemplate; + this.restOperations = restTemplateFactory.create(); } @Override diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultOAuth2UserService.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultOAuth2UserService.java index 0ca84ca6d44..4fbb5ec8fea 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultOAuth2UserService.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultOAuth2UserService.java @@ -39,7 +39,6 @@ import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestOperations; -import org.springframework.web.client.RestTemplate; /** * An implementation of an {@link OAuth2UserService} that supports standard OAuth 2.0 Provider's. @@ -73,9 +72,12 @@ public class DefaultOAuth2UserService implements OAuth2UserService authorizedClientRepository.saveAuthorizedClient(authorizedClient, principal, @@ -281,4 +288,16 @@ public Map apply(OAuth2AuthorizeRequest authorizeRequest) { return contextAttributes; } } + + private static OAuth2AuthorizedClientProvider createDefaultAuthorizedClientProvider( + OAuth2AuthorizedClientProviderBuilder.RefreshTokenGrantBuilderCustomizer refreshTokenGrantBuilderCustomizer, + OAuth2AuthorizedClientProviderBuilder.ClientCredentialsGrantBuilderCustomizer clientCredentialsGrantBuilderCustomizer, + OAuth2AuthorizedClientProviderBuilder.PasswordGrantBuilderCustomizer passwordGrantBuilderCustomizer) { + return OAuth2AuthorizedClientProviderBuilder.builder() + .authorizationCode() + .refreshToken(refreshTokenGrantBuilderCustomizer) + .clientCredentials(clientCredentialsGrantBuilderCustomizer) + .password(passwordGrantBuilderCustomizer) + .build(); + } } diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultReactiveOAuth2AuthorizedClientManager.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultReactiveOAuth2AuthorizedClientManager.java index a29a4c7e33c..850c16aecf9 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultReactiveOAuth2AuthorizedClientManager.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultReactiveOAuth2AuthorizedClientManager.java @@ -79,21 +79,13 @@ * @see ReactiveOAuth2AuthorizationFailureHandler */ public final class DefaultReactiveOAuth2AuthorizedClientManager implements ReactiveOAuth2AuthorizedClientManager { - private static final ReactiveOAuth2AuthorizedClientProvider DEFAULT_AUTHORIZED_CLIENT_PROVIDER = - ReactiveOAuth2AuthorizedClientProviderBuilder.builder() - .authorizationCode() - .refreshToken() - .clientCredentials() - .password() - .build(); - private static final Mono currentServerWebExchangeMono = Mono.subscriberContext() .filter(c -> c.hasKey(ServerWebExchange.class)) .map(c -> c.get(ServerWebExchange.class)); private final ReactiveClientRegistrationRepository clientRegistrationRepository; private final ServerOAuth2AuthorizedClientRepository authorizedClientRepository; - private ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = DEFAULT_AUTHORIZED_CLIENT_PROVIDER; + private ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider; private Function>> contextAttributesMapper = new DefaultContextAttributesMapper(); private ReactiveOAuth2AuthorizationSuccessHandler authorizationSuccessHandler; private ReactiveOAuth2AuthorizationFailureHandler authorizationFailureHandler; @@ -104,8 +96,18 @@ public final class DefaultReactiveOAuth2AuthorizedClientManager implements React * @param clientRegistrationRepository the repository of client registrations * @param authorizedClientRepository the repository of authorized clients */ - public DefaultReactiveOAuth2AuthorizedClientManager(ReactiveClientRegistrationRepository clientRegistrationRepository, - ServerOAuth2AuthorizedClientRepository authorizedClientRepository) { + public DefaultReactiveOAuth2AuthorizedClientManager( + ReactiveClientRegistrationRepository clientRegistrationRepository, + ServerOAuth2AuthorizedClientRepository authorizedClientRepository) { + this(clientRegistrationRepository, authorizedClientRepository, null, null, null); + } + + public DefaultReactiveOAuth2AuthorizedClientManager( + ReactiveClientRegistrationRepository clientRegistrationRepository, + ServerOAuth2AuthorizedClientRepository authorizedClientRepository, + ReactiveOAuth2AuthorizedClientProviderBuilder.RefreshTokenGrantBuilderCustomizer refreshTokenGrantBuilderCustomizer, + ReactiveOAuth2AuthorizedClientProviderBuilder.ClientCredentialsGrantBuilderCustomizer clientCredentialsGrantBuilderCustomizer, + ReactiveOAuth2AuthorizedClientProviderBuilder.PasswordGrantBuilderCustomizer passwordGrantBuilderCustomizer) { Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null"); Assert.notNull(authorizedClientRepository, "authorizedClientRepository cannot be null"); this.clientRegistrationRepository = clientRegistrationRepository; @@ -117,6 +119,10 @@ public DefaultReactiveOAuth2AuthorizedClientManager(ReactiveClientRegistrationRe (clientRegistrationId, principal, attributes) -> authorizedClientRepository.removeAuthorizedClient(clientRegistrationId, principal, (ServerWebExchange) attributes.get(ServerWebExchange.class.getName()))); + this.authorizedClientProvider = createDefaultAuthorizedClientProvider( + refreshTokenGrantBuilderCustomizer, + clientCredentialsGrantBuilderCustomizer, + passwordGrantBuilderCustomizer); } @Override @@ -292,4 +298,16 @@ public Mono> apply(OAuth2AuthorizeRequest authorizeRequest) .defaultIfEmpty(Collections.emptyMap()); } } + + private static ReactiveOAuth2AuthorizedClientProvider createDefaultAuthorizedClientProvider( + ReactiveOAuth2AuthorizedClientProviderBuilder.RefreshTokenGrantBuilderCustomizer refreshTokenGrantBuilderCustomizer, + ReactiveOAuth2AuthorizedClientProviderBuilder.ClientCredentialsGrantBuilderCustomizer clientCredentialsGrantBuilderCustomizer, + ReactiveOAuth2AuthorizedClientProviderBuilder.PasswordGrantBuilderCustomizer passwordGrantBuilderCustomizer) { + return ReactiveOAuth2AuthorizedClientProviderBuilder.builder() + .authorizationCode() + .refreshToken(refreshTokenGrantBuilderCustomizer) + .clientCredentials(clientCredentialsGrantBuilderCustomizer) + .password(passwordGrantBuilderCustomizer) + .build(); + } } diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/method/annotation/OAuth2AuthorizedClientArgumentResolver.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/method/annotation/OAuth2AuthorizedClientArgumentResolver.java index 8241f1bce19..e03de530063 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/method/annotation/OAuth2AuthorizedClientArgumentResolver.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/method/annotation/OAuth2AuthorizedClientArgumentResolver.java @@ -24,7 +24,9 @@ import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.client.ClientCredentialsOAuth2AuthorizedClientProvider; +import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; +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.annotation.RegisteredOAuth2AuthorizedClient; @@ -33,8 +35,6 @@ import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager; -import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -69,8 +69,8 @@ public final class OAuth2AuthorizedClientArgumentResolver implements HandlerMethodArgumentResolver { private static final Authentication ANONYMOUS_AUTHENTICATION = new AnonymousAuthenticationToken( "anonymous", "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")); - private OAuth2AuthorizedClientManager authorizedClientManager; - private boolean defaultAuthorizedClientManager; + private final OAuth2AuthorizedClientManager authorizedClientManager; + private final boolean defaultAuthorizedClientManager; /** * Constructs an {@code OAuth2AuthorizedClientArgumentResolver} using the provided parameters. @@ -81,6 +81,7 @@ public final class OAuth2AuthorizedClientArgumentResolver implements HandlerMeth public OAuth2AuthorizedClientArgumentResolver(OAuth2AuthorizedClientManager authorizedClientManager) { Assert.notNull(authorizedClientManager, "authorizedClientManager cannot be null"); this.authorizedClientManager = authorizedClientManager; + this.defaultAuthorizedClientManager = false; } /** @@ -89,12 +90,34 @@ public OAuth2AuthorizedClientArgumentResolver(OAuth2AuthorizedClientManager auth * @param clientRegistrationRepository the repository of client registrations * @param authorizedClientRepository the repository of authorized clients */ - public OAuth2AuthorizedClientArgumentResolver(ClientRegistrationRepository clientRegistrationRepository, - OAuth2AuthorizedClientRepository authorizedClientRepository) { + public OAuth2AuthorizedClientArgumentResolver( + ClientRegistrationRepository clientRegistrationRepository, + OAuth2AuthorizedClientRepository authorizedClientRepository) { + this(clientRegistrationRepository, authorizedClientRepository, null, null, null); + } + + /** + * Variant of {@link #OAuth2AuthorizedClientArgumentResolver(ClientRegistrationRepository, OAuth2AuthorizedClientRepository)} + * with {@literal refresh-token}, {@literal client-credentials}, and {@literal password} grant builder customizers. + * + * @param refreshTokenGrantBuilderCustomizer + * @param clientCredentialsGrantBuilderCustomizer + * @param passwordGrantBuilderCustomizer + */ + public OAuth2AuthorizedClientArgumentResolver( + ClientRegistrationRepository clientRegistrationRepository, + OAuth2AuthorizedClientRepository authorizedClientRepository, + OAuth2AuthorizedClientProviderBuilder.RefreshTokenGrantBuilderCustomizer refreshTokenGrantBuilderCustomizer, + OAuth2AuthorizedClientProviderBuilder.ClientCredentialsGrantBuilderCustomizer clientCredentialsGrantBuilderCustomizer, + OAuth2AuthorizedClientProviderBuilder.PasswordGrantBuilderCustomizer passwordGrantBuilderCustomizer) { Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null"); Assert.notNull(authorizedClientRepository, "authorizedClientRepository cannot be null"); this.authorizedClientManager = new DefaultOAuth2AuthorizedClientManager( - clientRegistrationRepository, authorizedClientRepository); + clientRegistrationRepository, + authorizedClientRepository, + refreshTokenGrantBuilderCustomizer, + clientCredentialsGrantBuilderCustomizer, + passwordGrantBuilderCustomizer); this.defaultAuthorizedClientManager = true; } diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServerOAuth2AuthorizedClientExchangeFilterFunction.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServerOAuth2AuthorizedClientExchangeFilterFunction.java index 23232bd30a5..29258437705 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServerOAuth2AuthorizedClientExchangeFilterFunction.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServerOAuth2AuthorizedClientExchangeFilterFunction.java @@ -186,8 +186,18 @@ public ServerOAuth2AuthorizedClientExchangeFilterFunction(ReactiveOAuth2Authoriz * @param clientRegistrationRepository the repository of client registrations * @param authorizedClientRepository the repository of authorized clients */ - public ServerOAuth2AuthorizedClientExchangeFilterFunction(ReactiveClientRegistrationRepository clientRegistrationRepository, - ServerOAuth2AuthorizedClientRepository authorizedClientRepository) { + public ServerOAuth2AuthorizedClientExchangeFilterFunction( + ReactiveClientRegistrationRepository clientRegistrationRepository, + ServerOAuth2AuthorizedClientRepository authorizedClientRepository) { + this(clientRegistrationRepository, authorizedClientRepository, null, null, null); + } + + public ServerOAuth2AuthorizedClientExchangeFilterFunction( + ReactiveClientRegistrationRepository clientRegistrationRepository, + ServerOAuth2AuthorizedClientRepository authorizedClientRepository, + ReactiveOAuth2AuthorizedClientProviderBuilder.RefreshTokenGrantBuilderCustomizer refreshTokenGrantBuilderCustomizer, + ReactiveOAuth2AuthorizedClientProviderBuilder.ClientCredentialsGrantBuilderCustomizer clientCredentialsGrantBuilderCustomizer, + ReactiveOAuth2AuthorizedClientProviderBuilder.PasswordGrantBuilderCustomizer passwordGrantBuilderCustomizer) { ReactiveOAuth2AuthorizationFailureHandler authorizationFailureHandler = new RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler( @@ -198,7 +208,10 @@ public ServerOAuth2AuthorizedClientExchangeFilterFunction(ReactiveClientRegistra this.authorizedClientManager = createDefaultAuthorizedClientManager( clientRegistrationRepository, authorizedClientRepository, - authorizationFailureHandler); + authorizationFailureHandler, + refreshTokenGrantBuilderCustomizer, + clientCredentialsGrantBuilderCustomizer, + passwordGrantBuilderCustomizer); this.clientResponseHandler = new AuthorizationFailureForwarder(authorizationFailureHandler); this.defaultAuthorizedClientManager = true; } @@ -206,7 +219,10 @@ public ServerOAuth2AuthorizedClientExchangeFilterFunction(ReactiveClientRegistra private static ReactiveOAuth2AuthorizedClientManager createDefaultAuthorizedClientManager( ReactiveClientRegistrationRepository clientRegistrationRepository, ServerOAuth2AuthorizedClientRepository authorizedClientRepository, - ReactiveOAuth2AuthorizationFailureHandler authorizationFailureHandler) { + ReactiveOAuth2AuthorizationFailureHandler authorizationFailureHandler, + ReactiveOAuth2AuthorizedClientProviderBuilder.RefreshTokenGrantBuilderCustomizer refreshTokenGrantBuilderCustomizer, + ReactiveOAuth2AuthorizedClientProviderBuilder.ClientCredentialsGrantBuilderCustomizer clientCredentialsGrantBuilderCustomizer, + ReactiveOAuth2AuthorizedClientProviderBuilder.PasswordGrantBuilderCustomizer passwordGrantBuilderCustomizer) { // gh-7544 if (authorizedClientRepository instanceof UnAuthenticatedServerOAuth2AuthorizedClientRepository) { @@ -218,16 +234,20 @@ private static ReactiveOAuth2AuthorizedClientManager createDefaultAuthorizedClie unauthenticatedAuthorizedClientManager.setAuthorizedClientProvider( ReactiveOAuth2AuthorizedClientProviderBuilder.builder() .authorizationCode() - .refreshToken() - .clientCredentials() - .password() + .refreshToken(refreshTokenGrantBuilderCustomizer) + .clientCredentials(clientCredentialsGrantBuilderCustomizer) + .password(passwordGrantBuilderCustomizer) .build()); return unauthenticatedAuthorizedClientManager; } DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager = new DefaultReactiveOAuth2AuthorizedClientManager( - clientRegistrationRepository, authorizedClientRepository); + clientRegistrationRepository, + authorizedClientRepository, + refreshTokenGrantBuilderCustomizer, + clientCredentialsGrantBuilderCustomizer, + passwordGrantBuilderCustomizer); authorizedClientManager.setAuthorizationFailureHandler(authorizationFailureHandler); return authorizedClientManager; diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunction.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunction.java index 8694121c8e2..2d1ef6a3aa6 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunction.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunction.java @@ -209,6 +209,15 @@ public ServletOAuth2AuthorizedClientExchangeFilterFunction(OAuth2AuthorizedClien public ServletOAuth2AuthorizedClientExchangeFilterFunction( ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientRepository authorizedClientRepository) { + this(clientRegistrationRepository, authorizedClientRepository, null, null, null); + } + + public ServletOAuth2AuthorizedClientExchangeFilterFunction( + ClientRegistrationRepository clientRegistrationRepository, + OAuth2AuthorizedClientRepository authorizedClientRepository, + OAuth2AuthorizedClientProviderBuilder.RefreshTokenGrantBuilderCustomizer refreshTokenGrantBuilderCustomizer, + OAuth2AuthorizedClientProviderBuilder.ClientCredentialsGrantBuilderCustomizer clientCredentialsGrantBuilderCustomizer, + OAuth2AuthorizedClientProviderBuilder.PasswordGrantBuilderCustomizer passwordGrantBuilderCustomizer) { OAuth2AuthorizationFailureHandler authorizationFailureHandler = new RemoveAuthorizedClientOAuth2AuthorizationFailureHandler( @@ -218,7 +227,11 @@ public ServletOAuth2AuthorizedClientExchangeFilterFunction( (HttpServletResponse) attributes.get(HttpServletResponse.class.getName()))); DefaultOAuth2AuthorizedClientManager defaultAuthorizedClientManager = new DefaultOAuth2AuthorizedClientManager( - clientRegistrationRepository, authorizedClientRepository); + clientRegistrationRepository, + authorizedClientRepository, + refreshTokenGrantBuilderCustomizer, + clientCredentialsGrantBuilderCustomizer, + passwordGrantBuilderCustomizer); defaultAuthorizedClientManager.setAuthorizationFailureHandler(authorizationFailureHandler); this.authorizedClientManager = defaultAuthorizedClientManager; this.defaultAuthorizedClientManager = true; @@ -296,6 +309,7 @@ public Consumer oauth2Configuration() { * {@link RequestContextHolder}. It also provides defaults for the {@link Authentication} using * {@link SecurityContextHolder}. It also can default the {@link OAuth2AuthorizedClient} using the * {@link #clientRegistrationId(String)} or the {@link #authentication(Authentication)}. + * * @return the {@link Consumer} to populate the attributes */ public Consumer> defaultRequest() { diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/result/method/annotation/OAuth2AuthorizedClientArgumentResolver.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/result/method/annotation/OAuth2AuthorizedClientArgumentResolver.java index 798fafc30cd..5da96468b1f 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/result/method/annotation/OAuth2AuthorizedClientArgumentResolver.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/result/method/annotation/OAuth2AuthorizedClientArgumentResolver.java @@ -26,6 +26,7 @@ import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager; +import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProviderBuilder; import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; @@ -81,12 +82,34 @@ public OAuth2AuthorizedClientArgumentResolver(ReactiveOAuth2AuthorizedClientMana * @param clientRegistrationRepository the repository of client registrations * @param authorizedClientRepository the repository of authorized clients */ - public OAuth2AuthorizedClientArgumentResolver(ReactiveClientRegistrationRepository clientRegistrationRepository, - ServerOAuth2AuthorizedClientRepository authorizedClientRepository) { + public OAuth2AuthorizedClientArgumentResolver( + ReactiveClientRegistrationRepository clientRegistrationRepository, + ServerOAuth2AuthorizedClientRepository authorizedClientRepository) { + this(clientRegistrationRepository, authorizedClientRepository, null, null, null); + } + + /** + * Variant of {@link #OAuth2AuthorizedClientArgumentResolver(ReactiveClientRegistrationRepository, ServerOAuth2AuthorizedClientRepository)} + * with {@literal refresh-token}, {@literal client-credentials}, and {@literal password} grant builder customizers. + * + * @param refreshTokenGrantBuilderCustomizer + * @param clientCredentialsGrantBuilderCustomizer + * @param passwordGrantBuilderCustomizer + */ + public OAuth2AuthorizedClientArgumentResolver( + ReactiveClientRegistrationRepository clientRegistrationRepository, + ServerOAuth2AuthorizedClientRepository authorizedClientRepository, + ReactiveOAuth2AuthorizedClientProviderBuilder.RefreshTokenGrantBuilderCustomizer refreshTokenGrantBuilderCustomizer, + ReactiveOAuth2AuthorizedClientProviderBuilder.ClientCredentialsGrantBuilderCustomizer clientCredentialsGrantBuilderCustomizer, + ReactiveOAuth2AuthorizedClientProviderBuilder.PasswordGrantBuilderCustomizer passwordGrantBuilderCustomizer) { Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null"); Assert.notNull(authorizedClientRepository, "authorizedClientRepository cannot be null"); this.authorizedClientManager = new DefaultReactiveOAuth2AuthorizedClientManager( - clientRegistrationRepository, authorizedClientRepository); + clientRegistrationRepository, + authorizedClientRepository, + refreshTokenGrantBuilderCustomizer, + clientCredentialsGrantBuilderCustomizer, + passwordGrantBuilderCustomizer); } @Override diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtDecoderProviderConfigurationUtils.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtDecoderProviderConfigurationUtils.java index 2496abb5b12..aade7c63bb4 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtDecoderProviderConfigurationUtils.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtDecoderProviderConfigurationUtils.java @@ -19,6 +19,7 @@ import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestOperations; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; @@ -39,17 +40,23 @@ class JwtDecoderProviderConfigurationUtils { private static final String OIDC_METADATA_PATH = "/.well-known/openid-configuration"; private static final String OAUTH_METADATA_PATH = "/.well-known/oauth-authorization-server"; - private static final RestTemplate rest = new RestTemplate(); + private static final RestTemplate DEFAULT_REST = new RestTemplate(); private static final ParameterizedTypeReference> typeReference = new ParameterizedTypeReference>() {}; - static Map getConfigurationForOidcIssuerLocation(String oidcIssuerLocation) { - return getConfiguration(oidcIssuerLocation, oidc(URI.create(oidcIssuerLocation))); + static Map getConfigurationForOidcIssuerLocation(String oidcIssuerLocation, + RestOperations restOperations) { + return getConfiguration(oidcIssuerLocation, restOperations == null ? DEFAULT_REST : restOperations, + oidc(URI.create(oidcIssuerLocation))); } - static Map getConfigurationForIssuerLocation(String issuer) { + static Map getConfigurationForIssuerLocation(String issuer, + RestOperations restOperations) { URI uri = URI.create(issuer); - return getConfiguration(issuer, oidc(uri), oidcRfc8414(uri), oauth(uri)); + return getConfiguration(issuer, restOperations == null ? DEFAULT_REST : restOperations, + oidc(uri), + oidcRfc8414(uri), + oauth(uri)); } static void validateIssuer(Map configuration, String issuer) { @@ -63,13 +70,13 @@ static void validateIssuer(Map configuration, String issuer) { } } - private static Map getConfiguration(String issuer, URI... uris) { + private static Map getConfiguration(String issuer, RestOperations restOperations, URI... uris) { String errorMessage = "Unable to resolve the Configuration with the provided Issuer of " + "\"" + issuer + "\""; for (URI uri : uris) { try { RequestEntity request = RequestEntity.get(uri).build(); - ResponseEntity> response = rest.exchange(request, typeReference); + ResponseEntity> response = restOperations.exchange(request, typeReference); Map configuration = response.getBody(); if (configuration.get("jwks_uri") == null) { diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtDecoders.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtDecoders.java index 0d2f2331987..4bf29ee9e9b 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtDecoders.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtDecoders.java @@ -19,6 +19,7 @@ import org.springframework.security.oauth2.core.OAuth2TokenValidator; import org.springframework.util.Assert; +import org.springframework.web.client.RestOperations; import static org.springframework.security.oauth2.jwt.NimbusJwtDecoder.withJwkSetUri; @@ -46,9 +47,18 @@ public final class JwtDecoders { * @return a {@link JwtDecoder} that was initialized by the OpenID Provider Configuration. */ public static JwtDecoder fromOidcIssuerLocation(String oidcIssuerLocation) { + return fromOidcIssuerLocation(oidcIssuerLocation, null); + } + + /** + * Variant of {@link #fromOidcIssuerLocation(String)} with customizable {@link RestOperations}. + * + * @since 5.3 + */ + public static JwtDecoder fromOidcIssuerLocation(String oidcIssuerLocation, RestOperations restOperations) { Assert.hasText(oidcIssuerLocation, "oidcIssuerLocation cannot be empty"); - Map configuration = JwtDecoderProviderConfigurationUtils.getConfigurationForOidcIssuerLocation(oidcIssuerLocation); - return withProviderConfiguration(configuration, oidcIssuerLocation); + Map configuration = JwtDecoderProviderConfigurationUtils.getConfigurationForOidcIssuerLocation(oidcIssuerLocation, restOperations); + return withProviderConfiguration(configuration, oidcIssuerLocation, restOperations); } /** @@ -84,9 +94,18 @@ public static JwtDecoder fromOidcIssuerLocation(String oidcIssuerLocation) { * @return a {@link JwtDecoder} that was initialized by one of the described endpoints */ public static JwtDecoder fromIssuerLocation(String issuer) { + return fromIssuerLocation(issuer, null); + } + + /** + * Variant of {@link #fromIssuerLocation(String)} with customizable {@link RestOperations}. + * + * @since 5.3 + */ + public static JwtDecoder fromIssuerLocation(String issuer, RestOperations restOperations) { Assert.hasText(issuer, "issuer cannot be empty"); - Map configuration = JwtDecoderProviderConfigurationUtils.getConfigurationForIssuerLocation(issuer); - return withProviderConfiguration(configuration, issuer); + Map configuration = JwtDecoderProviderConfigurationUtils.getConfigurationForIssuerLocation(issuer, restOperations); + return withProviderConfiguration(configuration, issuer, restOperations); } /** @@ -95,14 +114,22 @@ public static JwtDecoder fromIssuerLocation(String issuer) { * Configuration Response and Authorization Server Metadata * Response. * - * @param configuration the configuration values - * @param issuer the Issuer + * @param configuration the configuration values + * @param issuer the Issuer + * @param restOperations * @return {@link JwtDecoder} */ - private static JwtDecoder withProviderConfiguration(Map configuration, String issuer) { + private static JwtDecoder withProviderConfiguration( + Map configuration, + String issuer, + RestOperations restOperations) { JwtDecoderProviderConfigurationUtils.validateIssuer(configuration, issuer); OAuth2TokenValidator jwtValidator = JwtValidators.createDefaultWithIssuer(issuer); - NimbusJwtDecoder jwtDecoder = withJwkSetUri(configuration.get("jwks_uri").toString()).build(); + final NimbusJwtDecoder.JwkSetUriJwtDecoderBuilder builder = withJwkSetUri(configuration.get("jwks_uri").toString()); + if (restOperations != null) { + builder.restOperations(restOperations); + } + NimbusJwtDecoder jwtDecoder = builder.build(); jwtDecoder.setJwtValidator(jwtValidator); return jwtDecoder; diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java index b8e805fdfdf..4c59f0533a5 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java @@ -216,9 +216,10 @@ public static SecretKeyJwtDecoderBuilder withSecretKey(SecretKey secretKey) { * JWK Set uri. */ public static final class JwkSetUriJwtDecoderBuilder { + private static final RestTemplate DEFAULT_REST = new RestTemplate(); private String jwkSetUri; private Set signatureAlgorithms = new HashSet<>(); - private RestOperations restOperations = new RestTemplate(); + private RestOperations restOperations = DEFAULT_REST; private Cache cache; private JwkSetUriJwtDecoderBuilder(String jwkSetUri) { diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusReactiveJwtDecoder.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusReactiveJwtDecoder.java index c80bbb4a3a3..6b27914b6d6 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusReactiveJwtDecoder.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusReactiveJwtDecoder.java @@ -243,9 +243,10 @@ public static JwkSourceReactiveJwtDecoderBuilder withJwkSource(Function signatureAlgorithms = new HashSet<>(); - private WebClient webClient = WebClient.create(); + private WebClient webClient = DEFAULT_WEBCLIENT; private JwkSetUriReactiveJwtDecoderBuilder(String jwkSetUri) { Assert.hasText(jwkSetUri, "jwkSetUri cannot be empty"); diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/ReactiveJwtDecoders.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/ReactiveJwtDecoders.java index d062b515cfb..818c6c3f819 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/ReactiveJwtDecoders.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/ReactiveJwtDecoders.java @@ -19,6 +19,8 @@ import org.springframework.security.oauth2.core.OAuth2TokenValidator; import org.springframework.util.Assert; +import org.springframework.web.client.RestOperations; +import org.springframework.web.reactive.function.client.WebClient; import static org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder.withJwkSetUri; @@ -45,9 +47,15 @@ public final class ReactiveJwtDecoders { * @return a {@link ReactiveJwtDecoder} that was initialized by the OpenID Provider Configuration. */ public static ReactiveJwtDecoder fromOidcIssuerLocation(String oidcIssuerLocation) { + return fromOidcIssuerLocation(oidcIssuerLocation, null, null); + } + + public static ReactiveJwtDecoder fromOidcIssuerLocation(String oidcIssuerLocation, + RestOperations restOperations, + WebClient webClient) { Assert.hasText(oidcIssuerLocation, "oidcIssuerLocation cannot be empty"); - Map configuration = JwtDecoderProviderConfigurationUtils.getConfigurationForOidcIssuerLocation(oidcIssuerLocation); - return withProviderConfiguration(configuration, oidcIssuerLocation); + Map configuration = JwtDecoderProviderConfigurationUtils.getConfigurationForOidcIssuerLocation(oidcIssuerLocation, restOperations); + return withProviderConfiguration(configuration, oidcIssuerLocation, webClient); } /** @@ -83,9 +91,15 @@ public static ReactiveJwtDecoder fromOidcIssuerLocation(String oidcIssuerLocatio * @return a {@link ReactiveJwtDecoder} that was initialized by one of the described endpoints */ public static ReactiveJwtDecoder fromIssuerLocation(String issuer) { + return fromIssuerLocation(issuer, null, null); + } + + public static ReactiveJwtDecoder fromIssuerLocation(String issuer, + RestOperations restOperations, + WebClient webClient) { Assert.hasText(issuer, "issuer cannot be empty"); - Map configuration = JwtDecoderProviderConfigurationUtils.getConfigurationForIssuerLocation(issuer); - return withProviderConfiguration(configuration, issuer); + Map configuration = JwtDecoderProviderConfigurationUtils.getConfigurationForIssuerLocation(issuer, restOperations); + return withProviderConfiguration(configuration, issuer, webClient); } /** @@ -95,13 +109,20 @@ public static ReactiveJwtDecoder fromIssuerLocation(String issuer) { * Response. * * @param configuration the configuration values - * @param issuer the Issuer + * @param issuer the Issuer + * @param webClient * @return {@link ReactiveJwtDecoder} */ - private static ReactiveJwtDecoder withProviderConfiguration(Map configuration, String issuer) { + private static ReactiveJwtDecoder withProviderConfiguration(Map configuration, String issuer, + WebClient webClient) { JwtDecoderProviderConfigurationUtils.validateIssuer(configuration, issuer); OAuth2TokenValidator jwtValidator = JwtValidators.createDefaultWithIssuer(issuer); - NimbusReactiveJwtDecoder jwtDecoder = withJwkSetUri(configuration.get("jwks_uri").toString()).build(); + final NimbusReactiveJwtDecoder.JwkSetUriReactiveJwtDecoderBuilder builder = + withJwkSetUri(configuration.get("jwks_uri").toString()); + if (webClient != null) { + builder.webClient(webClient); + } + NimbusReactiveJwtDecoder jwtDecoder = builder.build(); jwtDecoder.setJwtValidator(jwtValidator); return jwtDecoder; diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtIssuerAuthenticationManagerResolver.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtIssuerAuthenticationManagerResolver.java index 3f132fbe2c6..73a0d35777c 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtIssuerAuthenticationManagerResolver.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtIssuerAuthenticationManagerResolver.java @@ -37,6 +37,7 @@ import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver; import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver; import org.springframework.util.Assert; +import org.springframework.web.client.RestOperations; /** * An implementation of {@link AuthenticationManagerResolver} that resolves a JWT-based {@link AuthenticationManager} @@ -63,7 +64,16 @@ public final class JwtIssuerAuthenticationManagerResolver implements Authenticat * @param trustedIssuers a whitelist of trusted issuers */ public JwtIssuerAuthenticationManagerResolver(String... trustedIssuers) { - this(Arrays.asList(trustedIssuers)); + this(null, trustedIssuers); + } + + /** + * Variant of {@link #JwtIssuerAuthenticationManagerResolver(String...)} with customizable {@link RestOperations}. + * + * @since 5.3 + */ + public JwtIssuerAuthenticationManagerResolver(RestOperations restOperations, String... trustedIssuers) { + this(Arrays.asList(trustedIssuers), restOperations); } /** @@ -72,10 +82,19 @@ public JwtIssuerAuthenticationManagerResolver(String... trustedIssuers) { * @param trustedIssuers a whitelist of trusted issuers */ public JwtIssuerAuthenticationManagerResolver(Collection trustedIssuers) { + this(trustedIssuers, null); + } + + /** + * Variant of {@link #JwtIssuerAuthenticationManagerResolver(Collection)} with customizable {@link RestOperations}. + * + * @since 5.3 + */ + public JwtIssuerAuthenticationManagerResolver(Collection trustedIssuers, RestOperations restOperations) { Assert.notEmpty(trustedIssuers, "trustedIssuers cannot be empty"); this.issuerAuthenticationManagerResolver = new TrustedIssuerJwtAuthenticationManagerResolver - (Collections.unmodifiableCollection(trustedIssuers)::contains); + (Collections.unmodifiableCollection(trustedIssuers)::contains, restOperations); } /** @@ -143,16 +162,18 @@ private static class TrustedIssuerJwtAuthenticationManagerResolver private final Map authenticationManagers = new ConcurrentHashMap<>(); private final Predicate trustedIssuer; + private final RestOperations restOperations; - TrustedIssuerJwtAuthenticationManagerResolver(Predicate trustedIssuer) { + TrustedIssuerJwtAuthenticationManagerResolver(Predicate trustedIssuer, RestOperations restOperations) { this.trustedIssuer = trustedIssuer; + this.restOperations = restOperations; } @Override public AuthenticationManager resolve(String issuer) { if (this.trustedIssuer.test(issuer)) { return this.authenticationManagers.computeIfAbsent(issuer, k -> { - JwtDecoder jwtDecoder = JwtDecoders.fromIssuerLocation(issuer); + JwtDecoder jwtDecoder = JwtDecoders.fromIssuerLocation(issuer, restOperations); return new JwtAuthenticationProvider(jwtDecoder)::authenticate; }); } diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtIssuerReactiveAuthenticationManagerResolver.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtIssuerReactiveAuthenticationManagerResolver.java index c2f415378b7..3cd1f7630c5 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtIssuerReactiveAuthenticationManagerResolver.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtIssuerReactiveAuthenticationManagerResolver.java @@ -38,6 +38,8 @@ import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException; import org.springframework.security.oauth2.server.resource.web.server.ServerBearerTokenAuthenticationConverter; import org.springframework.util.Assert; +import org.springframework.web.client.RestOperations; +import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.server.ServerWebExchange; /** @@ -69,7 +71,13 @@ public final class JwtIssuerReactiveAuthenticationManagerResolver * @param trustedIssuers a whitelist of trusted issuers */ public JwtIssuerReactiveAuthenticationManagerResolver(String... trustedIssuers) { - this(Arrays.asList(trustedIssuers)); + this(null, null, trustedIssuers); + } + + public JwtIssuerReactiveAuthenticationManagerResolver(RestOperations restOperations, + WebClient webClient, + String... trustedIssuers) { + this(Arrays.asList(trustedIssuers), restOperations, webClient); } /** @@ -78,9 +86,17 @@ public JwtIssuerReactiveAuthenticationManagerResolver(String... trustedIssuers) * @param trustedIssuers a whitelist of trusted issuers */ public JwtIssuerReactiveAuthenticationManagerResolver(Collection trustedIssuers) { + this(trustedIssuers, null, null); + } + + public JwtIssuerReactiveAuthenticationManagerResolver(Collection trustedIssuers, + RestOperations restOperations, + WebClient webClient) { Assert.notEmpty(trustedIssuers, "trustedIssuers cannot be empty"); this.issuerAuthenticationManagerResolver = - new TrustedIssuerJwtAuthenticationManagerResolver(new ArrayList<>(trustedIssuers)::contains); + new TrustedIssuerJwtAuthenticationManagerResolver(new ArrayList<>(trustedIssuers)::contains, + restOperations, + webClient); } /** @@ -155,9 +171,15 @@ private static class TrustedIssuerJwtAuthenticationManagerResolver private final Map> authenticationManagers = new ConcurrentHashMap<>(); private final Predicate trustedIssuer; + private final RestOperations restOperations; + private final WebClient webClient; - TrustedIssuerJwtAuthenticationManagerResolver(Predicate trustedIssuer) { + TrustedIssuerJwtAuthenticationManagerResolver(Predicate trustedIssuer, + RestOperations restOperations, + WebClient webClient) { this.trustedIssuer = trustedIssuer; + this.restOperations = restOperations; + this.webClient = webClient; } @Override @@ -167,7 +189,7 @@ public Mono resolve(String issuer) { } return this.authenticationManagers.computeIfAbsent(issuer, k -> Mono.fromCallable(() -> - new JwtReactiveAuthenticationManager(ReactiveJwtDecoders.fromIssuerLocation(k)) + new JwtReactiveAuthenticationManager(ReactiveJwtDecoders.fromIssuerLocation(k, restOperations, webClient)) ) .subscribeOn(Schedulers.boundedElastic()) .cache()); diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/DefaultOpaqueTokenIntrospectorRestTemplateFactory.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/DefaultOpaqueTokenIntrospectorRestTemplateFactory.java new file mode 100644 index 00000000000..f51366fa812 --- /dev/null +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/DefaultOpaqueTokenIntrospectorRestTemplateFactory.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.oauth2.server.resource.introspection; + +import org.springframework.http.client.support.BasicAuthenticationInterceptor; +import org.springframework.util.Assert; +import org.springframework.web.client.RestTemplate; + +/** + * Preconfigured and customizable {@link OpaqueTokenIntrospectorRestTemplateFactory} + */ +public class DefaultOpaqueTokenIntrospectorRestTemplateFactory implements OpaqueTokenIntrospectorRestTemplateFactory { + public static final OpaqueTokenIntrospectorRestTemplateFactory DEFAULT = new DefaultOpaqueTokenIntrospectorRestTemplateFactory(); + + private final Customizer customizer; + + public DefaultOpaqueTokenIntrospectorRestTemplateFactory() { + this(null); + } + + public DefaultOpaqueTokenIntrospectorRestTemplateFactory(Customizer customizer) { + this.customizer = customizer; + } + + @Override + public RestTemplate create(String clientId, String clientSecret) { + Assert.notNull(clientId, "clientId cannot be null"); + Assert.notNull(clientSecret, "clientSecret cannot be null"); + + RestTemplate restTemplate = new RestTemplate(); + restTemplate.getInterceptors().add(new BasicAuthenticationInterceptor(clientId, clientSecret)); + if (customizer != null) { + customizer.customize(restTemplate); + } + return restTemplate; + } +} diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusOpaqueTokenIntrospector.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusOpaqueTokenIntrospector.java index 1f0821cff78..0e6d9555ea8 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusOpaqueTokenIntrospector.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusOpaqueTokenIntrospector.java @@ -36,7 +36,6 @@ import org.springframework.http.MediaType; import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; -import org.springframework.http.client.support.BasicAuthenticationInterceptor; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal; @@ -45,7 +44,6 @@ import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestOperations; -import org.springframework.web.client.RestTemplate; import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.AUDIENCE; import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.CLIENT_ID; @@ -68,7 +66,7 @@ public class NimbusOpaqueTokenIntrospector implements OpaqueTokenIntrospector { private Converter> requestEntityConverter; private RestOperations restOperations; - private final String authorityPrefix = "SCOPE_"; + private String authorityPrefix = "SCOPE_"; /** * Creates a {@code OpaqueTokenAuthenticationProvider} with the provided parameters @@ -78,14 +76,24 @@ public class NimbusOpaqueTokenIntrospector implements OpaqueTokenIntrospector { * @param clientSecret The client's secret */ public NimbusOpaqueTokenIntrospector(String introspectionUri, String clientId, String clientSecret) { + this(introspectionUri, clientId, clientSecret, DefaultOpaqueTokenIntrospectorRestTemplateFactory.DEFAULT); + } + + /** + * Variant of {@link #NimbusOpaqueTokenIntrospector(String, String, String)} with customizable + * {@link OpaqueTokenIntrospectorRestTemplateFactory}. + * + * @since 5.3 + */ + public NimbusOpaqueTokenIntrospector(String introspectionUri, String clientId, String clientSecret, + OpaqueTokenIntrospectorRestTemplateFactory restTemplateFactory) { Assert.notNull(introspectionUri, "introspectionUri cannot be null"); Assert.notNull(clientId, "clientId cannot be null"); Assert.notNull(clientSecret, "clientSecret cannot be null"); + Assert.notNull(restTemplateFactory, "restTemplateFactory cannot be null"); this.requestEntityConverter = this.defaultRequestEntityConverter(URI.create(introspectionUri)); - RestTemplate restTemplate = new RestTemplate(); - restTemplate.getInterceptors().add(new BasicAuthenticationInterceptor(clientId, clientSecret)); - this.restOperations = restTemplate; + this.restOperations = restTemplateFactory.create(clientId, clientSecret); } /** @@ -161,6 +169,11 @@ public void setRequestEntityConverter(Converter> reques this.requestEntityConverter = requestEntityConverter; } + public void setAuthorityPrefix(String authorityPrefix) { + Assert.notNull(authorityPrefix, "authorityPrefix cannot be null"); + this.authorityPrefix = authorityPrefix; + } + private ResponseEntity makeRequest(RequestEntity requestEntity) { try { return this.restOperations.exchange(requestEntity, String.class); diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospector.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospector.java index 2aa31b792c8..9727d7ea4cb 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospector.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospector.java @@ -74,12 +74,19 @@ public class NimbusReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke * @param clientSecret The client secret for the authorized client */ public NimbusReactiveOpaqueTokenIntrospector(String introspectionUri, String clientId, String clientSecret) { + this(introspectionUri, clientId, clientSecret, null); + } + + public NimbusReactiveOpaqueTokenIntrospector(String introspectionUri, + String clientId, + String clientSecret, + WebClient webClient) { Assert.hasText(introspectionUri, "introspectionUri cannot be empty"); Assert.hasText(clientId, "clientId cannot be empty"); Assert.notNull(clientSecret, "clientSecret cannot be null"); this.introspectionUri = URI.create(introspectionUri); - this.webClient = WebClient.builder() + this.webClient = (webClient == null ? WebClient.builder() : webClient.mutate()) .defaultHeaders(h -> h.setBasicAuth(clientId, clientSecret)) .build(); } @@ -113,6 +120,11 @@ public Mono introspect(String token) { .onErrorMap(e -> !(e instanceof OAuth2IntrospectionException), this::onError); } + public void setAuthorityPrefix(String authorityPrefix) { + Assert.notNull(authorityPrefix, "authorityPrefix cannot be null"); + this.authorityPrefix = authorityPrefix; + } + private Mono makeRequest(String token) { return this.webClient.post() .uri(this.introspectionUri) diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/OpaqueTokenIntrospectorRestTemplateFactory.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/OpaqueTokenIntrospectorRestTemplateFactory.java new file mode 100644 index 00000000000..be673fa760c --- /dev/null +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/OpaqueTokenIntrospectorRestTemplateFactory.java @@ -0,0 +1,26 @@ +/* + * Copyright 2002-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.oauth2.server.resource.introspection; + +import org.springframework.web.client.RestTemplate; + +public interface OpaqueTokenIntrospectorRestTemplateFactory { + RestTemplate create(String clientId, String clientSecret); + + interface Customizer { + void customize(RestTemplate restTemplate); + } +}