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:
Dave Syer 2016-05-16 14:05:14 +01:00
parent 3734b666df
commit 684c8c81a3
5 changed files with 106 additions and 68 deletions

View File

@ -18,6 +18,7 @@ package org.springframework.boot.autoconfigure.security.oauth2.client;
import java.util.Collections;
import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoRestTemplateConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@ -83,7 +84,7 @@ class SsoSecurityConfigurer {
private OAuth2ClientAuthenticationProcessingFilter oauth2SsoFilter(
OAuth2SsoProperties sso) {
OAuth2RestOperations restTemplate = this.applicationContext
.getBean(OAuth2RestOperations.class);
.getBean(UserInfoRestTemplateConfiguration.class).userInfoRestTemplate();
ResourceServerTokenServices tokenServices = this.applicationContext
.getBean(ResourceServerTokenServices.class);
OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter(

View File

@ -25,7 +25,6 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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.ConditionalOnBean;
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.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.env.Environment;
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.ClientHttpResponse;
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.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import org.springframework.security.oauth2.client.token.AccessTokenRequest;
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.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
@ -80,65 +76,12 @@ import org.springframework.web.client.RestTemplate;
*/
@Configuration
@ConditionalOnMissingBean(AuthorizationServerEndpointsConfiguration.class)
@Import(UserInfoRestTemplateConfiguration.class)
public class ResourceServerTokenServicesConfiguration {
private static final Log logger = LogFactory
.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
@Conditional(NotJwtTokenCondition.class)
protected static class RemoteTokenServicesConfiguration {
@ -179,11 +122,11 @@ public class ResourceServerTokenServicesConfiguration {
public SocialTokenServicesConfiguration(ResourceServerProperties sso,
ObjectProvider<OAuth2ConnectionFactory<?>> connectionFactoryProvider,
@Qualifier("userInfoRestTemplate") ObjectProvider<OAuth2RestOperations> restTemplateProvider,
UserInfoRestTemplateConfiguration restTemplateProvider,
ObjectProvider<AuthoritiesExtractor> authoritiesExtractorProvider) {
this.sso = sso;
this.connectionFactory = connectionFactoryProvider.getIfAvailable();
this.restTemplate = restTemplateProvider.getIfAvailable();
this.restTemplate = restTemplateProvider.userInfoRestTemplate();
this.authoritiesExtractor = authoritiesExtractorProvider.getIfAvailable();
}
@ -223,10 +166,10 @@ public class ResourceServerTokenServicesConfiguration {
private final AuthoritiesExtractor authoritiesExtractor;
public UserInfoTokenServicesConfiguration(ResourceServerProperties sso,
@Qualifier("userInfoRestTemplate") ObjectProvider<OAuth2RestOperations> restTemplateProvider,
UserInfoRestTemplateConfiguration restTemplateProvider,
ObjectProvider<AuthoritiesExtractor> authoritiesExtractorProvider) {
this.sso = sso;
this.restTemplate = restTemplateProvider.getIfAvailable();
this.restTemplate = restTemplateProvider.userInfoRestTemplate();
this.authoritiesExtractor = authoritiesExtractorProvider.getIfAvailable();
}
@ -393,8 +336,7 @@ public class ResourceServerTokenServicesConfiguration {
}
private static class AcceptJsonRequestInterceptor
implements ClientHttpRequestInterceptor {
static class AcceptJsonRequestInterceptor implements ClientHttpRequestInterceptor {
@Override
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
public void enhance(AccessTokenRequest request,

View File

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

View File

@ -65,6 +65,7 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
import org.springframework.security.core.authority.AuthorityUtils;
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.token.grant.client.ClientCredentialsResourceDetails;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
@ -137,6 +138,8 @@ public class OAuth2AutoConfigurationTests {
assertThat(handler).isInstanceOf(ApprovalStoreUserApprovalHandler.class);
assertThat(clientDetails).isEqualTo(config);
verifyAuthentication(config);
assertThat(this.context.getBeanNamesForType(OAuth2RestOperations.class))
.isEmpty();
}
@Test

View File

@ -63,7 +63,7 @@ public class CustomRestTemplateBasicOAuth2SsoConfigurationTests {
public void customRestTemplateCanBePrimary() {
RestTemplate restTemplate = this.restTemplateProvider.getIfAvailable();
verifyZeroInteractions(restTemplate);
assertThat(this.applicationContext.getBeansOfType(RestTemplate.class)).hasSize(2);
assertThat(this.applicationContext.getBeansOfType(RestTemplate.class)).hasSize(1);
}
@Configuration