Allow custom override of user info OAuth2RestTemplate
Closes gh-5996
This commit is contained in:
parent
f72799186e
commit
ada441bccc
|
@ -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("<N/A>");
|
||||||
|
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<UserInfoRestTemplateCustomizer> customizers;
|
||||||
|
|
||||||
|
private final OAuth2ProtectedResourceDetails details;
|
||||||
|
|
||||||
|
private final OAuth2ClientContext oauth2ClientContext;
|
||||||
|
|
||||||
|
private OAuth2RestTemplate oauth2RestTemplate;
|
||||||
|
|
||||||
|
public DefaultUserInfoRestTemplateFactory(
|
||||||
|
ObjectProvider<List<UserInfoRestTemplateCustomizer>> customizers,
|
||||||
|
ObjectProvider<OAuth2ProtectedResourceDetails> details,
|
||||||
|
ObjectProvider<OAuth2ClientContext> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -88,7 +88,7 @@ public class ResourceServerTokenServicesConfiguration {
|
||||||
ObjectProvider<List<UserInfoRestTemplateCustomizer>> customizers,
|
ObjectProvider<List<UserInfoRestTemplateCustomizer>> customizers,
|
||||||
ObjectProvider<OAuth2ProtectedResourceDetails> details,
|
ObjectProvider<OAuth2ProtectedResourceDetails> details,
|
||||||
ObjectProvider<OAuth2ClientContext> oauth2ClientContext) {
|
ObjectProvider<OAuth2ClientContext> oauth2ClientContext) {
|
||||||
return new UserInfoRestTemplateFactory(customizers, details, oauth2ClientContext);
|
return new DefaultUserInfoRestTemplateFactory(customizers, details, oauth2ClientContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
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.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
|
* Factory used to create the {@link OAuth2RestTemplate} used for extracting user info
|
||||||
* authentication.
|
* during authentication if none is available.
|
||||||
*
|
*
|
||||||
* @author Dave Syer
|
* @author Dave Syer
|
||||||
|
* @author Stephane Nicoll
|
||||||
* @since 1.4.0
|
* @since 1.4.0
|
||||||
*/
|
*/
|
||||||
@Configuration
|
public interface UserInfoRestTemplateFactory {
|
||||||
public class UserInfoRestTemplateFactory {
|
|
||||||
|
|
||||||
private static final AuthorizationCodeResourceDetails DEFAULT_RESOURCE_DETAILS;
|
/**
|
||||||
|
* Return the {@link OAuth2RestTemplate} used for extracting user info during
|
||||||
static {
|
* authentication if none is available.
|
||||||
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
|
* @return the OAuth2RestTemplate used for authentication
|
||||||
details.setClientId("<N/A>");
|
*/
|
||||||
details.setUserAuthorizationUri("Not a URI because there is no client");
|
OAuth2RestTemplate getUserInfoRestTemplate();
|
||||||
details.setAccessTokenUri("Not a URI because there is no client");
|
|
||||||
DEFAULT_RESOURCE_DETAILS = details;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final List<UserInfoRestTemplateCustomizer> customizers;
|
|
||||||
|
|
||||||
private final OAuth2ProtectedResourceDetails details;
|
|
||||||
|
|
||||||
private final OAuth2ClientContext oauth2ClientContext;
|
|
||||||
|
|
||||||
private OAuth2RestTemplate template;
|
|
||||||
|
|
||||||
public UserInfoRestTemplateFactory(
|
|
||||||
ObjectProvider<List<UserInfoRestTemplateCustomizer>> customizers,
|
|
||||||
ObjectProvider<OAuth2ProtectedResourceDetails> details,
|
|
||||||
ObjectProvider<OAuth2ClientContext> 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.GrantedAuthority;
|
||||||
import org.springframework.security.core.authority.AuthorityUtils;
|
import org.springframework.security.core.authority.AuthorityUtils;
|
||||||
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
|
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.DefaultTokenServices;
|
||||||
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
|
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
|
||||||
import org.springframework.social.connect.ConnectionFactoryLocator;
|
import org.springframework.social.connect.ConnectionFactoryLocator;
|
||||||
|
@ -213,6 +214,19 @@ public class ResourceServerTokenServicesConfigurationTests {
|
||||||
assertThat(services).isNotNull();
|
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
|
@Configuration
|
||||||
@Import({ ResourceServerTokenServicesConfiguration.class,
|
@Import({ ResourceServerTokenServicesConfiguration.class,
|
||||||
ResourceServerPropertiesConfiguration.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]]
|
[[boot-features-security-custom-user-info]]
|
||||||
=== Customizing the User Info RestTemplate
|
=== Customizing the User Info RestTemplate
|
||||||
If you have a `user-info-uri`, the resource server features use an `OAuth2RestTemplate`
|
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
|
internally to fetch user details for authentication. This is provided as a `@Bean` of
|
||||||
`@Bean` with id `userInfoRestTemplate`, but you shouldn't need to know that to just
|
type `UserInfoRestTemplateFactory`. The default should be fine for most providers, but
|
||||||
use it. The default should be fine for most providers, but occasionally you might need to
|
occasionally you might need to add additional interceptors, or change the request
|
||||||
add additional interceptors, or change the request authenticator (which is how the token
|
authenticator (which is how the token gets attached to outgoing requests). To add a
|
||||||
gets attached to outgoing requests). To add a customization just create a bean of type
|
customization just create a bean of type `UserInfoRestTemplateCustomizer` - it has a
|
||||||
`UserInfoRestTemplateCustomizer` - it has a single method that will be called after the
|
single method that will be called after the bean is created but before it is initialized.
|
||||||
bean is created but before it is initialized. The rest template that is being customized
|
The rest template that is being customized here is _only_ used internally to carry out
|
||||||
here is _only_ used internally to carry out authentication.
|
authentication. Alternatively, you could define your own `UserInfoRestTemplateFactory`
|
||||||
|
`@Bean` to take full control.
|
||||||
|
|
||||||
[TIP]
|
[TIP]
|
||||||
====
|
====
|
||||||
|
|
Loading…
Reference in New Issue