Rationalize some features and merge in customizers from Spring Cloud
This commit is contained in:
@@ -15,9 +15,6 @@
|
||||
*/
|
||||
package org.springframework.boot.autoconfigure.security.oauth2.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
|
||||
@@ -27,6 +24,10 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnNotWebApplication;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.autoconfigure.security.oauth2.ClientCredentialsProperties;
|
||||
import org.springframework.boot.context.embedded.FilterRegistrationBean;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
@@ -35,25 +36,22 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.context.annotation.ScopedProxyMode;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.client.ClientHttpRequestExecution;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext;
|
||||
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.filter.OAuth2ClientContextFilter;
|
||||
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.DefaultAccessTokenRequest;
|
||||
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
|
||||
import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails;
|
||||
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;
|
||||
import org.springframework.security.oauth2.config.annotation.web.configuration.OAuth2ClientConfiguration;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.security.oauth2.provider.OAuth2Authentication;
|
||||
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
@@ -61,84 +59,119 @@ import org.springframework.util.MultiValueMap;
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass(EnableOAuth2Client.class)
|
||||
@ConditionalOnBean(OAuth2ClientConfiguration.class)
|
||||
@ConditionalOnExpression("'${spring.oauth2.client.clientId:}'!=''")
|
||||
public class SpringSecurityOAuth2ClientConfiguration {
|
||||
|
||||
private static final Log logger = LogFactory
|
||||
.getLog(SpringSecurityOAuth2ClientConfiguration.class);
|
||||
|
||||
@Autowired
|
||||
private ClientCredentialsProperties credentials;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
String prefix = "spring.oauth2.client";
|
||||
boolean defaultSecret = this.credentials.isDefaultSecret();
|
||||
logger.info(String.format(
|
||||
"Initialized OAuth2 Client\n\n%s.clientId = %s\n%s.secret = %s\n\n",
|
||||
prefix, this.credentials.getClientId(), prefix,
|
||||
defaultSecret ? this.credentials.getClientSecret() : "****"));
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
public OAuth2RestTemplate oauth2RestTemplate(OAuth2ClientContext oauth2ClientContext,
|
||||
OAuth2ProtectedResourceDetails details) {
|
||||
OAuth2RestTemplate template = new OAuth2RestTemplate(details, oauth2ClientContext);
|
||||
return template;
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class ClientAuthenticationFilterConfiguration {
|
||||
|
||||
@Resource
|
||||
@Qualifier("accessTokenRequest")
|
||||
private AccessTokenRequest accessTokenRequest;
|
||||
|
||||
@Autowired
|
||||
private ClientCredentialsProperties credentials;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
String prefix = "spring.oauth2.client";
|
||||
boolean defaultSecret = this.credentials.isDefaultSecret();
|
||||
logger.info(String.format(
|
||||
"Initialized OAuth2 Client\n\n%s.clientId = %s\n%s.secret = %s\n\n",
|
||||
prefix, this.credentials.getClientId(), prefix,
|
||||
defaultSecret ? this.credentials.getClientSecret() : "****"));
|
||||
}
|
||||
protected abstract static class BaseConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConfigurationProperties("spring.oauth2.client")
|
||||
@Primary
|
||||
public AuthorizationCodeResourceDetails authorizationCodeResourceDetails() {
|
||||
public AuthorizationCodeResourceDetails oauth2RemoteResource() {
|
||||
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
|
||||
details.setClientSecret(this.credentials.getClientSecret());
|
||||
details.setClientId(this.credentials.getClientId());
|
||||
return details;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnNotWebApplication
|
||||
protected static class SingletonScopedConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConfigurationProperties("spring.oauth2.client")
|
||||
@Primary
|
||||
public ClientCredentialsResourceDetails oauth2RemoteResource() {
|
||||
ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails();
|
||||
return details;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public OAuth2ClientContext oauth2ClientContext() {
|
||||
return new DefaultOAuth2ClientContext(new DefaultAccessTokenRequest());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnBean(OAuth2ClientConfiguration.class)
|
||||
@ConditionalOnWebApplication
|
||||
protected static class SessionScopedConfiguration extends BaseConfiguration {
|
||||
|
||||
@Resource
|
||||
@Qualifier("accessTokenRequest")
|
||||
protected AccessTokenRequest accessTokenRequest;
|
||||
|
||||
@Bean
|
||||
@Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES)
|
||||
public OAuth2ClientContext oauth2ClientContext() {
|
||||
return new DefaultOAuth2ClientContext(accessTokenRequest);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FilterRegistrationBean oauth2ClientFilterRegistration(
|
||||
OAuth2ClientContextFilter filter) {
|
||||
FilterRegistrationBean registration = new FilterRegistrationBean();
|
||||
registration.setFilter(filter);
|
||||
registration.setOrder(0);
|
||||
registration.setOrder(-100);
|
||||
return registration;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public OAuth2RestOperations authorizationCodeRestTemplate(
|
||||
AuthorizationCodeResourceDetails oauth2RemoteResource) {
|
||||
OAuth2RestTemplate template = new OAuth2RestTemplate(oauth2RemoteResource,
|
||||
oauth2ClientContext());
|
||||
template.setInterceptors(Arrays
|
||||
.<ClientHttpRequestInterceptor> asList(new ClientHttpRequestInterceptor() {
|
||||
@Override
|
||||
public ClientHttpResponse intercept(HttpRequest request,
|
||||
byte[] body, ClientHttpRequestExecution execution)
|
||||
throws IOException {
|
||||
request.getHeaders().setAccept(
|
||||
Arrays.asList(MediaType.APPLICATION_JSON));
|
||||
return execution.execute(request, body);
|
||||
}
|
||||
}));
|
||||
AuthorizationCodeAccessTokenProvider accessTokenProvider = new AuthorizationCodeAccessTokenProvider();
|
||||
accessTokenProvider.setTokenRequestEnhancer(new RequestEnhancer() {
|
||||
@Override
|
||||
public void enhance(AccessTokenRequest request,
|
||||
OAuth2ProtectedResourceDetails resource,
|
||||
MultiValueMap<String, String> form, HttpHeaders headers) {
|
||||
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
|
||||
}
|
||||
});
|
||||
template.setAccessTokenProvider(accessTokenProvider);
|
||||
return template;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* When the authentication is per cookie but the stored token is an oauth2 one, we can
|
||||
* pass that on to a client that wants to call downstream. We don't even need an
|
||||
* OAuth2ClientContextFilter until we need to refresh the access token. To handle
|
||||
* refresh tokens you need to <code>@EnableOAuth2Client</code>
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnMissingBean(OAuth2ClientConfiguration.class)
|
||||
@ConditionalOnWebApplication
|
||||
protected static class RequestScopedConfiguration extends BaseConfiguration {
|
||||
|
||||
@Bean
|
||||
@Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES)
|
||||
public OAuth2ClientContext oauth2ClientContext() {
|
||||
return new DefaultOAuth2ClientContext(this.accessTokenRequest);
|
||||
DefaultOAuth2ClientContext context = new DefaultOAuth2ClientContext(
|
||||
new DefaultAccessTokenRequest());
|
||||
Authentication principal = SecurityContextHolder.getContext()
|
||||
.getAuthentication();
|
||||
if (principal instanceof OAuth2Authentication) {
|
||||
OAuth2Authentication authentication = (OAuth2Authentication) principal;
|
||||
Object details = authentication.getDetails();
|
||||
if (details instanceof OAuth2AuthenticationDetails) {
|
||||
OAuth2AuthenticationDetails oauthsDetails = (OAuth2AuthenticationDetails) details;
|
||||
String token = oauthsDetails.getTokenValue();
|
||||
context.setAccessToken(new DefaultOAuth2AccessToken(token));
|
||||
}
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2013-2015 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 org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
|
||||
|
||||
public interface JwtAccessTokenConverterConfigurer {
|
||||
|
||||
void configure(JwtAccessTokenConverter converter);
|
||||
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerEndpointsConfiguration;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.Errors;
|
||||
@@ -66,6 +67,11 @@ public class ResourceServerProperties implements Validator, BeanFactoryAware {
|
||||
*/
|
||||
private boolean preferTokenInfo = true;
|
||||
|
||||
/**
|
||||
* The token type to send when using the userInfoUri.
|
||||
*/
|
||||
private String tokenType = DefaultOAuth2AccessToken.BEARER_TYPE;
|
||||
|
||||
private Jwt jwt = new Jwt();
|
||||
|
||||
public ResourceServerProperties() {
|
||||
@@ -126,6 +132,14 @@ public class ResourceServerProperties implements Validator, BeanFactoryAware {
|
||||
this.preferTokenInfo = preferTokenInfo;
|
||||
}
|
||||
|
||||
public String getTokenType() {
|
||||
return tokenType;
|
||||
}
|
||||
|
||||
public void setTokenType(String tokenType) {
|
||||
this.tokenType = tokenType;
|
||||
}
|
||||
|
||||
public Jwt getJwt() {
|
||||
return this.jwt;
|
||||
}
|
||||
|
||||
@@ -15,32 +15,48 @@
|
||||
*/
|
||||
package org.springframework.boot.autoconfigure.security.oauth2.resource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
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;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
|
||||
import org.springframework.boot.autoconfigure.security.oauth2.ClientCredentialsProperties;
|
||||
import org.springframework.boot.autoconfigure.security.oauth2.client.SpringSecurityOAuth2ClientConfiguration;
|
||||
import org.springframework.boot.autoconfigure.security.oauth2.client.SpringSecurityOAuth2ClientConfiguration.ClientAuthenticationFilterConfiguration;
|
||||
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.OrderComparator;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.http.MediaType;
|
||||
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.config.annotation.web.configuration.EnableOAuth2Client;
|
||||
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
|
||||
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
|
||||
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
|
||||
@@ -49,6 +65,7 @@ import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenCo
|
||||
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
|
||||
import org.springframework.social.connect.ConnectionFactoryLocator;
|
||||
import org.springframework.social.connect.support.OAuth2ConnectionFactory;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.client.ResourceAccessException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
@@ -64,29 +81,88 @@ 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");
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
private List<UserInfoRestTemplateCustomizer> customizers = Collections
|
||||
.emptyList();
|
||||
|
||||
@Autowired(required = false)
|
||||
private OAuth2ProtectedResourceDetails details;
|
||||
|
||||
@Autowired(required = false)
|
||||
private OAuth2ClientContext oauth2ClientContext;
|
||||
|
||||
@Bean(name = "userInfoRestTemplate")
|
||||
public OAuth2RestTemplate userInfoRestTemplate() {
|
||||
OAuth2RestTemplate template;
|
||||
if (details == null) {
|
||||
details = DEFAULT_RESOURCE_DETAILS;
|
||||
}
|
||||
if (oauth2ClientContext == null) {
|
||||
template = new OAuth2RestTemplate(details);
|
||||
}
|
||||
else {
|
||||
template = new OAuth2RestTemplate(details, oauth2ClientContext);
|
||||
}
|
||||
template.setInterceptors(Arrays
|
||||
.<ClientHttpRequestInterceptor> asList(new ClientHttpRequestInterceptor() {
|
||||
@Override
|
||||
public ClientHttpResponse intercept(HttpRequest request,
|
||||
byte[] body, ClientHttpRequestExecution execution)
|
||||
throws IOException {
|
||||
request.getHeaders().setAccept(
|
||||
Arrays.asList(MediaType.APPLICATION_JSON));
|
||||
return execution.execute(request, body);
|
||||
}
|
||||
}));
|
||||
AuthorizationCodeAccessTokenProvider accessTokenProvider = new AuthorizationCodeAccessTokenProvider();
|
||||
accessTokenProvider.setTokenRequestEnhancer(new RequestEnhancer() {
|
||||
@Override
|
||||
public void enhance(AccessTokenRequest request,
|
||||
OAuth2ProtectedResourceDetails resource,
|
||||
MultiValueMap<String, String> form, HttpHeaders headers) {
|
||||
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
|
||||
}
|
||||
});
|
||||
template.setAccessTokenProvider(accessTokenProvider);
|
||||
OrderComparator.sort(customizers);
|
||||
for (UserInfoRestTemplateCustomizer customizer : customizers) {
|
||||
customizer.customize(template);
|
||||
}
|
||||
return template;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Conditional(NotJwtToken.class)
|
||||
@EnableOAuth2Client
|
||||
@Import(ClientAuthenticationFilterConfiguration.class)
|
||||
protected static class RemoteTokenServicesConfiguration {
|
||||
|
||||
@Configuration
|
||||
@Import(SpringSecurityOAuth2ClientConfiguration.class)
|
||||
@Conditional(TokenInfo.class)
|
||||
protected static class TokenInfoServicesConfiguration {
|
||||
|
||||
@Autowired
|
||||
private ResourceServerProperties resource;
|
||||
|
||||
@Autowired
|
||||
private AuthorizationCodeResourceDetails client;
|
||||
|
||||
@Bean
|
||||
public ResourceServerTokenServices remoteTokenServices() {
|
||||
RemoteTokenServices services = new RemoteTokenServices();
|
||||
services.setCheckTokenEndpointUrl(this.resource.getTokenInfoUri());
|
||||
services.setClientId(this.client.getClientId());
|
||||
services.setClientSecret(this.client.getClientSecret());
|
||||
services.setClientId(this.resource.getClientId());
|
||||
services.setClientSecret(this.resource.getClientSecret());
|
||||
return services;
|
||||
}
|
||||
|
||||
@@ -100,21 +176,19 @@ public class ResourceServerTokenServicesConfiguration {
|
||||
@Autowired
|
||||
private ResourceServerProperties sso;
|
||||
|
||||
@Autowired
|
||||
private ClientCredentialsProperties client;
|
||||
|
||||
@Autowired(required = false)
|
||||
private OAuth2ConnectionFactory<?> connectionFactory;
|
||||
|
||||
@Autowired(required = false)
|
||||
private Map<String, OAuth2RestOperations> resources = Collections.emptyMap();
|
||||
@Qualifier("userInfoRestTemplate")
|
||||
private OAuth2RestOperations restTemplate;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(ConnectionFactoryLocator.class)
|
||||
@ConditionalOnMissingBean(ResourceServerTokenServices.class)
|
||||
public SpringSocialTokenServices socialTokenServices() {
|
||||
return new SpringSocialTokenServices(this.connectionFactory,
|
||||
this.client.getClientId());
|
||||
this.sso.getClientId());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -122,33 +196,33 @@ public class ResourceServerTokenServicesConfiguration {
|
||||
ResourceServerTokenServices.class })
|
||||
public ResourceServerTokenServices userInfoTokenServices() {
|
||||
UserInfoTokenServices services = new UserInfoTokenServices(
|
||||
this.sso.getUserInfoUri(), this.client.getClientId());
|
||||
services.setResources(this.resources);
|
||||
this.sso.getUserInfoUri(), this.sso.getClientId());
|
||||
services.setTokenType(sso.getTokenType());
|
||||
services.setRestTemplate(restTemplate);
|
||||
return services;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnMissingClass(name = "org.springframework.social.connect.support.OAuth2ConnectionFactory")
|
||||
@ConditionalOnMissingClass("org.springframework.social.connect.support.OAuth2ConnectionFactory")
|
||||
@Conditional(NotTokenInfo.class)
|
||||
protected static class UserInfoTokenServicesConfiguration {
|
||||
|
||||
@Autowired
|
||||
private ResourceServerProperties sso;
|
||||
|
||||
@Autowired
|
||||
private ClientCredentialsProperties client;
|
||||
|
||||
@Autowired(required = false)
|
||||
private Map<String, OAuth2RestOperations> resources = Collections.emptyMap();
|
||||
@Qualifier("userInfoRestTemplate")
|
||||
private OAuth2RestOperations restTemplate;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(ResourceServerTokenServices.class)
|
||||
public ResourceServerTokenServices userInfoTokenServices() {
|
||||
UserInfoTokenServices services = new UserInfoTokenServices(
|
||||
this.sso.getUserInfoUri(), this.client.getClientId());
|
||||
services.setResources(this.resources);
|
||||
this.sso.getUserInfoUri(), this.sso.getClientId());
|
||||
services.setRestTemplate(restTemplate);
|
||||
services.setTokenType(sso.getTokenType());
|
||||
return services;
|
||||
}
|
||||
|
||||
@@ -160,9 +234,15 @@ public class ResourceServerTokenServicesConfiguration {
|
||||
@Conditional(JwtToken.class)
|
||||
protected static class JwtTokenServicesConfiguration {
|
||||
|
||||
private RestTemplate keyUriRestTemplate = new RestTemplate();
|
||||
|
||||
@Autowired
|
||||
private ResourceServerProperties resource;
|
||||
|
||||
@Autowired(required = false)
|
||||
private List<JwtAccessTokenConverterConfigurer> configurers = Collections
|
||||
.emptyList();
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(ResourceServerTokenServices.class)
|
||||
public ResourceServerTokenServices jwtTokenServices() {
|
||||
@@ -182,22 +262,34 @@ public class ResourceServerTokenServicesConfiguration {
|
||||
String keyValue = this.resource.getJwt().getKeyValue();
|
||||
if (!StringUtils.hasText(keyValue)) {
|
||||
try {
|
||||
keyValue = (String) new RestTemplate().getForObject(
|
||||
this.resource.getJwt().getKeyUri(), Map.class).get("value");
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
if (resource.getClientId() != null
|
||||
&& resource.getClientSecret() != null) {
|
||||
byte[] token = Base64
|
||||
.encode((resource.getClientId() + ":" + resource
|
||||
.getClientSecret()).getBytes());
|
||||
headers.add("Authorization", "Basic " + new String(token));
|
||||
}
|
||||
HttpEntity<Void> requestEntity = new HttpEntity<Void>(headers);
|
||||
keyValue = (String) keyUriRestTemplate
|
||||
.exchange(resource.getJwt().getKeyUri(), HttpMethod.GET,
|
||||
requestEntity, Map.class).getBody().get("value");
|
||||
}
|
||||
catch (ResourceAccessException e) {
|
||||
// ignore
|
||||
logger.warn("Failed to fetch token key (you may need to refresh when the auth server is back)");
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (StringUtils.hasText(keyValue) && !keyValue.startsWith("-----BEGIN")) {
|
||||
converter.setSigningKey(keyValue);
|
||||
}
|
||||
if (StringUtils.hasText(keyValue) && !keyValue.startsWith("-----BEGIN")) {
|
||||
converter.setSigningKey(keyValue);
|
||||
}
|
||||
if (keyValue != null) {
|
||||
converter.setVerifierKey(keyValue);
|
||||
}
|
||||
AnnotationAwareOrderComparator.sort(configurers);
|
||||
for (JwtAccessTokenConverterConfigurer configurer : configurers) {
|
||||
configurer.configure(converter);
|
||||
}
|
||||
return converter;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2014-2015 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 org.springframework.security.oauth2.client.OAuth2RestTemplate;
|
||||
|
||||
/**
|
||||
* Callback for customizing the rest template used to fetch user details if authentication
|
||||
* is done via OAuth2 access tokens. 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). The rest
|
||||
* template that is being customized here is <i>only</i> used internally to carry out
|
||||
* authentication (in the SSO or Resource Server use cases).
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public interface UserInfoRestTemplateCustomizer {
|
||||
|
||||
/**
|
||||
* Customize the rest template before it is initialized.
|
||||
*
|
||||
* @param template the rest template
|
||||
*/
|
||||
void customize(OAuth2RestTemplate template);
|
||||
|
||||
}
|
||||
@@ -15,11 +15,7 @@
|
||||
*/
|
||||
package org.springframework.boot.autoconfigure.security.oauth2.resource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
@@ -44,22 +40,21 @@ public class UserInfoTokenServices implements ResourceServerTokenServices {
|
||||
|
||||
private String clientId;
|
||||
|
||||
private Collection<OAuth2RestOperations> resources = Collections.emptySet();
|
||||
private OAuth2RestOperations restTemplate;
|
||||
|
||||
private String tokenType = DefaultOAuth2AccessToken.BEARER_TYPE;
|
||||
|
||||
public UserInfoTokenServices(String userInfoEndpointUrl, String clientId) {
|
||||
this.userInfoEndpointUrl = userInfoEndpointUrl;
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public void setTokenType(String tokenType) {
|
||||
this.tokenType = tokenType;
|
||||
}
|
||||
|
||||
public void setResources(Map<String, OAuth2RestOperations> resources) {
|
||||
this.resources = new ArrayList<OAuth2RestOperations>();
|
||||
for (Entry<String, OAuth2RestOperations> key : resources.entrySet()) {
|
||||
OAuth2RestOperations value = key.getValue();
|
||||
String clientIdForTemplate = value.getResource().getClientId();
|
||||
if (clientIdForTemplate!=null && clientIdForTemplate.equals(clientId)) {
|
||||
this.resources.add(value);
|
||||
}
|
||||
}
|
||||
public void setRestTemplate(OAuth2RestOperations restTemplate) {
|
||||
this.restTemplate = restTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -87,8 +82,8 @@ public class UserInfoTokenServices implements ResourceServerTokenServices {
|
||||
}
|
||||
|
||||
private Object getPrincipal(Map<String, Object> map) {
|
||||
String[] keys = new String[] { "user", "username", "userid", "user_id", "login",
|
||||
"id" };
|
||||
String[] keys = new String[] { "user", "username", "userid", "user_id", "login",
|
||||
"id", "name" };
|
||||
for (String key : keys) {
|
||||
if (map.containsKey(key)) {
|
||||
return map.get(key);
|
||||
@@ -104,23 +99,15 @@ public class UserInfoTokenServices implements ResourceServerTokenServices {
|
||||
|
||||
private Map<String, Object> getMap(String path, String accessToken) {
|
||||
logger.info("Getting user info from: " + path);
|
||||
OAuth2RestOperations restTemplate = null;
|
||||
for (OAuth2RestOperations candidate : resources) {
|
||||
try {
|
||||
if (accessToken.equals(candidate.getAccessToken().getValue())) {
|
||||
restTemplate = candidate;
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
}
|
||||
}
|
||||
OAuth2RestOperations restTemplate = this.restTemplate;
|
||||
if (restTemplate == null) {
|
||||
BaseOAuth2ProtectedResourceDetails resource = new BaseOAuth2ProtectedResourceDetails();
|
||||
resource.setClientId(clientId);
|
||||
restTemplate = new OAuth2RestTemplate(resource);
|
||||
restTemplate.getOAuth2ClientContext().setAccessToken(
|
||||
new DefaultOAuth2AccessToken(accessToken));
|
||||
}
|
||||
DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(accessToken);
|
||||
token.setTokenType(tokenType);
|
||||
restTemplate.getOAuth2ClientContext().setAccessToken(token);
|
||||
@SuppressWarnings("rawtypes")
|
||||
Map map = restTemplate.getForEntity(path, Map.class).getBody();
|
||||
@SuppressWarnings("unchecked")
|
||||
|
||||
@@ -51,8 +51,8 @@ org.springframework.boot.autoconfigure.reactor.ReactorAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.redis.RedisAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.security.oauth2.SpringSecurityOAuth2AutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.social.FacebookAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.social.LinkedInAutoConfiguration,\
|
||||
|
||||
@@ -19,7 +19,7 @@ package org.springframework.boot.autoconfigure;
|
||||
import org.springframework.boot.test.AbstractConfigurationClassTests;
|
||||
|
||||
/**
|
||||
* Tests for the autoconfigure module's @Configuration classes
|
||||
* Tests for the autoconfigure module's <code>@Configuration</code> classes
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class AutoConfigureConfigurationClassTests extends AbstractConfigurationClassTests {
|
||||
|
||||
@@ -16,12 +16,15 @@
|
||||
|
||||
package org.springframework.boot.autoconfigure.security.oauth2;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;
|
||||
@@ -74,7 +77,6 @@ import org.springframework.security.oauth2.provider.approval.ApprovalStoreUserAp
|
||||
import org.springframework.security.oauth2.provider.approval.TokenApprovalStore;
|
||||
import org.springframework.security.oauth2.provider.approval.UserApprovalHandler;
|
||||
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
|
||||
import org.springframework.security.oauth2.provider.client.InMemoryClientDetailsService;
|
||||
import org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint;
|
||||
import org.springframework.security.oauth2.provider.expression.OAuth2MethodSecurityExpressionHandler;
|
||||
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
|
||||
@@ -90,10 +92,6 @@ import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
/**
|
||||
* Verify Spring Security OAuth2 auto-configuration secures end points properly, accepts
|
||||
* environmental overrides, and also backs off in the presence of other
|
||||
|
||||
@@ -17,7 +17,6 @@ package org.springframework.boot.autoconfigure.security.oauth2.resource;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -58,14 +57,14 @@ public class UserInfoTokenServicesTests {
|
||||
|
||||
@Test
|
||||
public void sunnyDay() {
|
||||
services.setResources(Collections.singletonMap("foo", template));
|
||||
services.setRestTemplate(template);
|
||||
assertEquals("unknown", services.loadAuthentication("FOO").getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void userId() {
|
||||
map.put("userid", "spencer");
|
||||
services.setResources(Collections.singletonMap("foo", template));
|
||||
services.setRestTemplate(template);
|
||||
assertEquals("spencer", services.loadAuthentication("FOO").getName());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user