diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/DefaultUserInfoRestTemplateFactory.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/DefaultUserInfoRestTemplateFactory.java new file mode 100644 index 00000000000..c1be6054e74 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/DefaultUserInfoRestTemplateFactory.java @@ -0,0 +1,95 @@ +/* + * Copyright 2012-2017 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.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; + +/** + * Factory used to create the {@link OAuth2RestTemplate} used for extracting user info + * during authentication if none is available. + * + * @author Dave Syer + * @author Stephane Nicoll + * @since 1.5.0 + */ +public class DefaultUserInfoRestTemplateFactory implements UserInfoRestTemplateFactory { + + private static final AuthorizationCodeResourceDetails DEFAULT_RESOURCE_DETAILS; + + static { + AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails(); + details.setClientId(""); + details.setUserAuthorizationUri("Not a URI because there is no client"); + details.setAccessTokenUri("Not a URI because there is no client"); + DEFAULT_RESOURCE_DETAILS = details; + } + + private final List customizers; + + private final OAuth2ProtectedResourceDetails details; + + private final OAuth2ClientContext oauth2ClientContext; + + private OAuth2RestTemplate oauth2RestTemplate; + + public DefaultUserInfoRestTemplateFactory( + ObjectProvider> customizers, + ObjectProvider details, + ObjectProvider oauth2ClientContext) { + this.customizers = customizers.getIfAvailable(); + this.details = details.getIfAvailable(); + this.oauth2ClientContext = oauth2ClientContext.getIfAvailable(); + } + + public OAuth2RestTemplate getUserInfoRestTemplate() { + if (this.oauth2RestTemplate == null) { + this.oauth2RestTemplate = createOAuth2RestTemplate( + this.details == null ? DEFAULT_RESOURCE_DETAILS : this.details); + this.oauth2RestTemplate.getInterceptors().add(new AcceptJsonRequestInterceptor()); + AuthorizationCodeAccessTokenProvider accessTokenProvider = new AuthorizationCodeAccessTokenProvider(); + accessTokenProvider.setTokenRequestEnhancer(new AcceptJsonRequestEnhancer()); + this.oauth2RestTemplate.setAccessTokenProvider(accessTokenProvider); + if (!CollectionUtils.isEmpty(this.customizers)) { + AnnotationAwareOrderComparator.sort(this.customizers); + for (UserInfoRestTemplateCustomizer customizer : this.customizers) { + customizer.customize(this.oauth2RestTemplate); + } + } + } + return this.oauth2RestTemplate; + } + + private OAuth2RestTemplate createOAuth2RestTemplate( + OAuth2ProtectedResourceDetails details) { + if (this.oauth2ClientContext == null) { + return new OAuth2RestTemplate(details); + } + return new OAuth2RestTemplate(details, this.oauth2ClientContext); + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/ResourceServerTokenServicesConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/ResourceServerTokenServicesConfiguration.java index 08e5508d3fd..f5729b6b269 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/ResourceServerTokenServicesConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/ResourceServerTokenServicesConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 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. @@ -88,7 +88,7 @@ public class ResourceServerTokenServicesConfiguration { ObjectProvider> customizers, ObjectProvider details, ObjectProvider oauth2ClientContext) { - return new UserInfoRestTemplateFactory(customizers, details, oauth2ClientContext); + return new DefaultUserInfoRestTemplateFactory(customizers, details, oauth2ClientContext); } @Configuration diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/UserInfoRestTemplateFactory.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/UserInfoRestTemplateFactory.java index ba17849d44c..2595230b757 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/UserInfoRestTemplateFactory.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/UserInfoRestTemplateFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 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. @@ -16,80 +16,23 @@ 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; /** - * Factory used to create the rest template used for extracting user info during - * authentication. + * Factory used to create the {@link OAuth2RestTemplate} used for extracting user info + * during authentication if none is available. * * @author Dave Syer + * @author Stephane Nicoll * @since 1.4.0 */ -@Configuration -public class UserInfoRestTemplateFactory { +public interface UserInfoRestTemplateFactory { - private static final AuthorizationCodeResourceDetails DEFAULT_RESOURCE_DETAILS; - - static { - AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails(); - details.setClientId(""); - details.setUserAuthorizationUri("Not a URI because there is no client"); - details.setAccessTokenUri("Not a URI because there is no client"); - DEFAULT_RESOURCE_DETAILS = details; - } - - private final List customizers; - - private final OAuth2ProtectedResourceDetails details; - - private final OAuth2ClientContext oauth2ClientContext; - - private OAuth2RestTemplate template; - - public UserInfoRestTemplateFactory( - ObjectProvider> customizers, - ObjectProvider details, - ObjectProvider oauth2ClientContext) { - this.customizers = customizers.getIfAvailable(); - this.details = details.getIfAvailable(); - this.oauth2ClientContext = oauth2ClientContext.getIfAvailable(); - } - - public OAuth2RestTemplate getUserInfoRestTemplate() { - 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); - } + /** + * Return the {@link OAuth2RestTemplate} used for extracting user info during + * authentication if none is available. + * @return the OAuth2RestTemplate used for authentication + */ + OAuth2RestTemplate getUserInfoRestTemplate(); } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/ResourceServerTokenServicesConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/ResourceServerTokenServicesConfigurationTests.java index 8539f023b54..9e3a1962651 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/ResourceServerTokenServicesConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/ResourceServerTokenServicesConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 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. @@ -48,6 +48,7 @@ import org.springframework.http.client.ClientHttpResponse; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.oauth2.client.OAuth2RestTemplate; +import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails; import org.springframework.security.oauth2.provider.token.DefaultTokenServices; import org.springframework.security.oauth2.provider.token.RemoteTokenServices; import org.springframework.social.connect.ConnectionFactoryLocator; @@ -213,6 +214,19 @@ public class ResourceServerTokenServicesConfigurationTests { assertThat(services).isNotNull(); } + @Test + public void customUserInfoRestTemplateFactory() { + EnvironmentTestUtils.addEnvironment(this.environment, + "security.oauth2.resource.userInfoUri:http://example.com"); + this.context = new SpringApplicationBuilder( + CustomUserInfoRestTemplateFactory.class, ResourceConfiguration.class) + .environment(this.environment).web(false).run(); + assertThat(this.context.getBeansOfType(UserInfoRestTemplateFactory.class)) + .hasSize(1); + assertThat(this.context.getBean(UserInfoRestTemplateFactory.class)) + .isInstanceOf(CustomUserInfoRestTemplateFactory.class); + } + @Configuration @Import({ ResourceServerTokenServicesConfiguration.class, ResourceServerPropertiesConfiguration.class, @@ -313,4 +327,18 @@ public class ResourceServerTokenServicesConfigurationTests { } + @Component + protected static class CustomUserInfoRestTemplateFactory + implements UserInfoRestTemplateFactory { + + private final OAuth2RestTemplate restTemplate = + new OAuth2RestTemplate(new AuthorizationCodeResourceDetails()); + + @Override + public OAuth2RestTemplate getUserInfoRestTemplate() { + return this.restTemplate; + } + + } + } diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index f914fbf45ec..5957e593059 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -2555,14 +2555,15 @@ which suits most providers and matches the spec, but if you need to change it yo [[boot-features-security-custom-user-info]] === Customizing the User Info RestTemplate If you have a `user-info-uri`, the resource server features use an `OAuth2RestTemplate` -internally to fetch user details for authentication. This is provided as a qualified -`@Bean` with id `userInfoRestTemplate`, but you shouldn't need to know that to just -use it. The default should be fine for most providers, but occasionally you might need to -add additional interceptors, or change the request authenticator (which is how the token -gets attached to outgoing requests). To add a customization just create a bean of type -`UserInfoRestTemplateCustomizer` - it has a single method that will be called after the -bean is created but before it is initialized. The rest template that is being customized -here is _only_ used internally to carry out authentication. +internally to fetch user details for authentication. This is provided as a `@Bean` of +type `UserInfoRestTemplateFactory`. The default should be fine for most providers, but +occasionally you might need to add additional interceptors, or change the request +authenticator (which is how the token gets attached to outgoing requests). To add a +customization just create a bean of type `UserInfoRestTemplateCustomizer` - it has a +single method that will be called after the bean is created but before it is initialized. +The rest template that is being customized here is _only_ used internally to carry out +authentication. Alternatively, you could define your own `UserInfoRestTemplateFactory` +`@Bean` to take full control. [TIP] ====