Extract user info rest template into a factory
Instead of using a @Bean, it is better to use an opaque factory for an internal dependency that users are not going to want in the context. OAuth2RestTemplate is a common enough bean type that creating on in autoconfig makes it hard for users to add their own for business use. See gh-5967
This commit is contained in:
parent
3734b666df
commit
684c8c81a3
|
|
@ -18,6 +18,7 @@ package org.springframework.boot.autoconfigure.security.oauth2.client;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoRestTemplateConfiguration;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
|
|
@ -83,7 +84,7 @@ class SsoSecurityConfigurer {
|
||||||
private OAuth2ClientAuthenticationProcessingFilter oauth2SsoFilter(
|
private OAuth2ClientAuthenticationProcessingFilter oauth2SsoFilter(
|
||||||
OAuth2SsoProperties sso) {
|
OAuth2SsoProperties sso) {
|
||||||
OAuth2RestOperations restTemplate = this.applicationContext
|
OAuth2RestOperations restTemplate = this.applicationContext
|
||||||
.getBean(OAuth2RestOperations.class);
|
.getBean(UserInfoRestTemplateConfiguration.class).userInfoRestTemplate();
|
||||||
ResourceServerTokenServices tokenServices = this.applicationContext
|
ResourceServerTokenServices tokenServices = this.applicationContext
|
||||||
.getBean(ResourceServerTokenServices.class);
|
.getBean(ResourceServerTokenServices.class);
|
||||||
OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter(
|
OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter(
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@ import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
import org.springframework.beans.factory.ObjectProvider;
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
|
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
|
|
@ -37,6 +36,7 @@ import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.ConditionContext;
|
import org.springframework.context.annotation.ConditionContext;
|
||||||
import org.springframework.context.annotation.Conditional;
|
import org.springframework.context.annotation.Conditional;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||||
import org.springframework.core.env.Environment;
|
import org.springframework.core.env.Environment;
|
||||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||||
|
|
@ -49,14 +49,10 @@ import org.springframework.http.client.ClientHttpRequestExecution;
|
||||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||||
import org.springframework.http.client.ClientHttpResponse;
|
import org.springframework.http.client.ClientHttpResponse;
|
||||||
import org.springframework.security.crypto.codec.Base64;
|
import org.springframework.security.crypto.codec.Base64;
|
||||||
import org.springframework.security.oauth2.client.OAuth2ClientContext;
|
|
||||||
import org.springframework.security.oauth2.client.OAuth2RestOperations;
|
import org.springframework.security.oauth2.client.OAuth2RestOperations;
|
||||||
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
|
|
||||||
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
|
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
|
||||||
import org.springframework.security.oauth2.client.token.AccessTokenRequest;
|
import org.springframework.security.oauth2.client.token.AccessTokenRequest;
|
||||||
import org.springframework.security.oauth2.client.token.RequestEnhancer;
|
import org.springframework.security.oauth2.client.token.RequestEnhancer;
|
||||||
import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider;
|
|
||||||
import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails;
|
|
||||||
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerEndpointsConfiguration;
|
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerEndpointsConfiguration;
|
||||||
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
|
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
|
||||||
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
|
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
|
||||||
|
|
@ -80,65 +76,12 @@ import org.springframework.web.client.RestTemplate;
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
@ConditionalOnMissingBean(AuthorizationServerEndpointsConfiguration.class)
|
@ConditionalOnMissingBean(AuthorizationServerEndpointsConfiguration.class)
|
||||||
|
@Import(UserInfoRestTemplateConfiguration.class)
|
||||||
public class ResourceServerTokenServicesConfiguration {
|
public class ResourceServerTokenServicesConfiguration {
|
||||||
|
|
||||||
private static final Log logger = LogFactory
|
private static final Log logger = LogFactory
|
||||||
.getLog(ResourceServerTokenServicesConfiguration.class);
|
.getLog(ResourceServerTokenServicesConfiguration.class);
|
||||||
|
|
||||||
@Configuration
|
|
||||||
protected static class UserInfoRestTemplateConfiguration {
|
|
||||||
|
|
||||||
private static final AuthorizationCodeResourceDetails DEFAULT_RESOURCE_DETAILS = new AuthorizationCodeResourceDetails();
|
|
||||||
|
|
||||||
static {
|
|
||||||
DEFAULT_RESOURCE_DETAILS.setClientId("<N/A>");
|
|
||||||
DEFAULT_RESOURCE_DETAILS
|
|
||||||
.setUserAuthorizationUri("Not a URI " + "because there is no client");
|
|
||||||
DEFAULT_RESOURCE_DETAILS
|
|
||||||
.setAccessTokenUri("Not a URI " + "because there is no client");
|
|
||||||
}
|
|
||||||
|
|
||||||
private final List<UserInfoRestTemplateCustomizer> customizers;
|
|
||||||
|
|
||||||
private final OAuth2ProtectedResourceDetails details;
|
|
||||||
|
|
||||||
private final OAuth2ClientContext oauth2ClientContext;
|
|
||||||
|
|
||||||
public UserInfoRestTemplateConfiguration(
|
|
||||||
ObjectProvider<List<UserInfoRestTemplateCustomizer>> customizersProvider,
|
|
||||||
ObjectProvider<OAuth2ProtectedResourceDetails> detailsProvider,
|
|
||||||
ObjectProvider<OAuth2ClientContext> oauth2ClientContextProvider) {
|
|
||||||
this.customizers = customizersProvider.getIfAvailable();
|
|
||||||
this.details = detailsProvider.getIfAvailable();
|
|
||||||
this.oauth2ClientContext = oauth2ClientContextProvider.getIfAvailable();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean(name = "userInfoRestTemplate")
|
|
||||||
public OAuth2RestTemplate userInfoRestTemplate() {
|
|
||||||
OAuth2RestTemplate template = getTemplate(
|
|
||||||
this.details == null ? DEFAULT_RESOURCE_DETAILS : this.details);
|
|
||||||
template.getInterceptors().add(new AcceptJsonRequestInterceptor());
|
|
||||||
AuthorizationCodeAccessTokenProvider accessTokenProvider = new AuthorizationCodeAccessTokenProvider();
|
|
||||||
accessTokenProvider.setTokenRequestEnhancer(new AcceptJsonRequestEnhancer());
|
|
||||||
template.setAccessTokenProvider(accessTokenProvider);
|
|
||||||
if (!CollectionUtils.isEmpty(this.customizers)) {
|
|
||||||
AnnotationAwareOrderComparator.sort(this.customizers);
|
|
||||||
for (UserInfoRestTemplateCustomizer customizer : this.customizers) {
|
|
||||||
customizer.customize(template);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return template;
|
|
||||||
}
|
|
||||||
|
|
||||||
private OAuth2RestTemplate getTemplate(OAuth2ProtectedResourceDetails details) {
|
|
||||||
if (this.oauth2ClientContext == null) {
|
|
||||||
return new OAuth2RestTemplate(details);
|
|
||||||
}
|
|
||||||
return new OAuth2RestTemplate(details, this.oauth2ClientContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@Conditional(NotJwtTokenCondition.class)
|
@Conditional(NotJwtTokenCondition.class)
|
||||||
protected static class RemoteTokenServicesConfiguration {
|
protected static class RemoteTokenServicesConfiguration {
|
||||||
|
|
@ -179,11 +122,11 @@ public class ResourceServerTokenServicesConfiguration {
|
||||||
|
|
||||||
public SocialTokenServicesConfiguration(ResourceServerProperties sso,
|
public SocialTokenServicesConfiguration(ResourceServerProperties sso,
|
||||||
ObjectProvider<OAuth2ConnectionFactory<?>> connectionFactoryProvider,
|
ObjectProvider<OAuth2ConnectionFactory<?>> connectionFactoryProvider,
|
||||||
@Qualifier("userInfoRestTemplate") ObjectProvider<OAuth2RestOperations> restTemplateProvider,
|
UserInfoRestTemplateConfiguration restTemplateProvider,
|
||||||
ObjectProvider<AuthoritiesExtractor> authoritiesExtractorProvider) {
|
ObjectProvider<AuthoritiesExtractor> authoritiesExtractorProvider) {
|
||||||
this.sso = sso;
|
this.sso = sso;
|
||||||
this.connectionFactory = connectionFactoryProvider.getIfAvailable();
|
this.connectionFactory = connectionFactoryProvider.getIfAvailable();
|
||||||
this.restTemplate = restTemplateProvider.getIfAvailable();
|
this.restTemplate = restTemplateProvider.userInfoRestTemplate();
|
||||||
this.authoritiesExtractor = authoritiesExtractorProvider.getIfAvailable();
|
this.authoritiesExtractor = authoritiesExtractorProvider.getIfAvailable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -223,10 +166,10 @@ public class ResourceServerTokenServicesConfiguration {
|
||||||
private final AuthoritiesExtractor authoritiesExtractor;
|
private final AuthoritiesExtractor authoritiesExtractor;
|
||||||
|
|
||||||
public UserInfoTokenServicesConfiguration(ResourceServerProperties sso,
|
public UserInfoTokenServicesConfiguration(ResourceServerProperties sso,
|
||||||
@Qualifier("userInfoRestTemplate") ObjectProvider<OAuth2RestOperations> restTemplateProvider,
|
UserInfoRestTemplateConfiguration restTemplateProvider,
|
||||||
ObjectProvider<AuthoritiesExtractor> authoritiesExtractorProvider) {
|
ObjectProvider<AuthoritiesExtractor> authoritiesExtractorProvider) {
|
||||||
this.sso = sso;
|
this.sso = sso;
|
||||||
this.restTemplate = restTemplateProvider.getIfAvailable();
|
this.restTemplate = restTemplateProvider.userInfoRestTemplate();
|
||||||
this.authoritiesExtractor = authoritiesExtractorProvider.getIfAvailable();
|
this.authoritiesExtractor = authoritiesExtractorProvider.getIfAvailable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -393,8 +336,7 @@ public class ResourceServerTokenServicesConfiguration {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class AcceptJsonRequestInterceptor
|
static class AcceptJsonRequestInterceptor implements ClientHttpRequestInterceptor {
|
||||||
implements ClientHttpRequestInterceptor {
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
|
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
|
||||||
|
|
@ -405,7 +347,7 @@ public class ResourceServerTokenServicesConfiguration {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class AcceptJsonRequestEnhancer implements RequestEnhancer {
|
static class AcceptJsonRequestEnhancer implements RequestEnhancer {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void enhance(AccessTokenRequest request,
|
public void enhance(AccessTokenRequest request,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2016 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
|
||||||
|
*
|
||||||
|
* http://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.boot.autoconfigure.security.oauth2.resource;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
|
import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerTokenServicesConfiguration.AcceptJsonRequestEnhancer;
|
||||||
|
import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerTokenServicesConfiguration.AcceptJsonRequestInterceptor;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||||
|
import org.springframework.security.oauth2.client.OAuth2ClientContext;
|
||||||
|
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
|
||||||
|
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
|
||||||
|
import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider;
|
||||||
|
import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dave Syer
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class UserInfoRestTemplateConfiguration {
|
||||||
|
|
||||||
|
private static final AuthorizationCodeResourceDetails DEFAULT_RESOURCE_DETAILS = new AuthorizationCodeResourceDetails();
|
||||||
|
|
||||||
|
static {
|
||||||
|
DEFAULT_RESOURCE_DETAILS.setClientId("<N/A>");
|
||||||
|
DEFAULT_RESOURCE_DETAILS
|
||||||
|
.setUserAuthorizationUri("Not a URI " + "because there is no client");
|
||||||
|
DEFAULT_RESOURCE_DETAILS
|
||||||
|
.setAccessTokenUri("Not a URI " + "because there is no client");
|
||||||
|
}
|
||||||
|
|
||||||
|
private final List<UserInfoRestTemplateCustomizer> customizers;
|
||||||
|
|
||||||
|
private final OAuth2ProtectedResourceDetails details;
|
||||||
|
|
||||||
|
private final OAuth2ClientContext oauth2ClientContext;
|
||||||
|
|
||||||
|
private OAuth2RestTemplate template;
|
||||||
|
|
||||||
|
public UserInfoRestTemplateConfiguration(
|
||||||
|
ObjectProvider<List<UserInfoRestTemplateCustomizer>> customizersProvider,
|
||||||
|
ObjectProvider<OAuth2ProtectedResourceDetails> detailsProvider,
|
||||||
|
ObjectProvider<OAuth2ClientContext> oauth2ClientContextProvider) {
|
||||||
|
this.customizers = customizersProvider.getIfAvailable();
|
||||||
|
this.details = detailsProvider.getIfAvailable();
|
||||||
|
this.oauth2ClientContext = oauth2ClientContextProvider.getIfAvailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not a @Bean: use this method as a factory
|
||||||
|
public OAuth2RestTemplate userInfoRestTemplate() {
|
||||||
|
if (this.template == null) {
|
||||||
|
this.template = getTemplate(
|
||||||
|
this.details == null ? DEFAULT_RESOURCE_DETAILS : this.details);
|
||||||
|
this.template.getInterceptors().add(new AcceptJsonRequestInterceptor());
|
||||||
|
AuthorizationCodeAccessTokenProvider accessTokenProvider = new AuthorizationCodeAccessTokenProvider();
|
||||||
|
accessTokenProvider.setTokenRequestEnhancer(new AcceptJsonRequestEnhancer());
|
||||||
|
this.template.setAccessTokenProvider(accessTokenProvider);
|
||||||
|
if (!CollectionUtils.isEmpty(this.customizers)) {
|
||||||
|
AnnotationAwareOrderComparator.sort(this.customizers);
|
||||||
|
for (UserInfoRestTemplateCustomizer customizer : this.customizers) {
|
||||||
|
customizer.customize(this.template);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.template;
|
||||||
|
}
|
||||||
|
|
||||||
|
private OAuth2RestTemplate getTemplate(OAuth2ProtectedResourceDetails details) {
|
||||||
|
if (this.oauth2ClientContext == null) {
|
||||||
|
return new OAuth2RestTemplate(details);
|
||||||
|
}
|
||||||
|
return new OAuth2RestTemplate(details, this.oauth2ClientContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -65,6 +65,7 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
|
||||||
import org.springframework.security.core.authority.AuthorityUtils;
|
import org.springframework.security.core.authority.AuthorityUtils;
|
||||||
import org.springframework.security.crypto.codec.Base64;
|
import org.springframework.security.crypto.codec.Base64;
|
||||||
import org.springframework.security.oauth2.client.OAuth2ClientContext;
|
import org.springframework.security.oauth2.client.OAuth2ClientContext;
|
||||||
|
import org.springframework.security.oauth2.client.OAuth2RestOperations;
|
||||||
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
|
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
|
||||||
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
|
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
|
||||||
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
|
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
|
||||||
|
|
@ -137,6 +138,8 @@ public class OAuth2AutoConfigurationTests {
|
||||||
assertThat(handler).isInstanceOf(ApprovalStoreUserApprovalHandler.class);
|
assertThat(handler).isInstanceOf(ApprovalStoreUserApprovalHandler.class);
|
||||||
assertThat(clientDetails).isEqualTo(config);
|
assertThat(clientDetails).isEqualTo(config);
|
||||||
verifyAuthentication(config);
|
verifyAuthentication(config);
|
||||||
|
assertThat(this.context.getBeanNamesForType(OAuth2RestOperations.class))
|
||||||
|
.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ public class CustomRestTemplateBasicOAuth2SsoConfigurationTests {
|
||||||
public void customRestTemplateCanBePrimary() {
|
public void customRestTemplateCanBePrimary() {
|
||||||
RestTemplate restTemplate = this.restTemplateProvider.getIfAvailable();
|
RestTemplate restTemplate = this.restTemplateProvider.getIfAvailable();
|
||||||
verifyZeroInteractions(restTemplate);
|
verifyZeroInteractions(restTemplate);
|
||||||
assertThat(this.applicationContext.getBeansOfType(RestTemplate.class)).hasSize(2);
|
assertThat(this.applicationContext.getBeansOfType(RestTemplate.class)).hasSize(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue