@EnableOAuth2*

This commit is contained in:
Dave Syer
2014-09-01 08:17:23 +01:00
parent 4282f4aeab
commit db9bfb0b56
20 changed files with 511 additions and 177 deletions

View File

@@ -62,6 +62,10 @@
<version>2.0.3.RELEASE</version> <version>2.0.3.RELEASE</version>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-core</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
@@ -82,11 +86,6 @@
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.apache.ant</groupId>
<artifactId>ant-launcher</artifactId>
<version>1.9.3</version>
</dependency>
</dependencies> </dependencies>
<properties> <properties>

View File

@@ -30,17 +30,12 @@ import org.cloudfoundry.community.servicebroker.service.ServiceInstanceService;
import org.springframework.aop.framework.ProxyFactory; import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.cloud.cloudfoundry.broker.FreeServiceDefinitionFactory; import org.springframework.cloud.cloudfoundry.broker.FreeServiceDefinitionFactory;
import org.springframework.cloud.cloudfoundry.broker.ServiceInstanceBindingRepository; import org.springframework.cloud.cloudfoundry.broker.ServiceInstanceBindingRepository;
import org.springframework.cloud.cloudfoundry.broker.ServiceInstanceRepository; import org.springframework.cloud.cloudfoundry.broker.ServiceInstanceRepository;
@@ -50,6 +45,12 @@ import org.springframework.cloud.cloudfoundry.broker.simple.SimpleServiceInstanc
import org.springframework.cloud.cloudfoundry.broker.simple.SimpleServiceInstanceService; import org.springframework.cloud.cloudfoundry.broker.simple.SimpleServiceInstanceService;
import org.springframework.cloud.netflix.eureka.advice.PiggybackMethodInterceptor; import org.springframework.cloud.netflix.eureka.advice.PiggybackMethodInterceptor;
import org.springframework.cloud.netflix.eureka.event.EurekaRegistryAvailableEvent; import org.springframework.cloud.netflix.eureka.event.EurekaRegistryAvailableEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
@@ -83,14 +84,14 @@ import com.netflix.eureka.lease.LeaseManager;
public class ServiceBrokerAutoConfiguration { public class ServiceBrokerAutoConfiguration {
@Configuration @Configuration
@ConditionalOnMissingClass(name = "com.netflix.eureka.PeerAwareInstanceRegistry") @ConditionalOnMissingBean(EurekaInstanceConfig.class)
@ConditionalOnMissingBean(CatalogService.class)
protected static class CatalogConfiguration { protected static class CatalogConfiguration {
@Autowired @Autowired
private BrokerProperties broker; private BrokerProperties broker;
@Bean @Bean
@ConditionalOnMissingBean(CatalogService.class)
public BeanCatalogService catalogService() { public BeanCatalogService catalogService() {
return new BeanCatalogService(catalog()); return new BeanCatalogService(catalog());
} }
@@ -143,7 +144,7 @@ public class ServiceBrokerAutoConfiguration {
} }
@Configuration @Configuration
@ConditionalOnClass(PeerAwareInstanceRegistry.class) @ConditionalOnBean(EurekaInstanceConfig.class)
protected static class EurekaCatalogConfiguration { protected static class EurekaCatalogConfiguration {
@Bean @Bean
@@ -156,6 +157,7 @@ public class ServiceBrokerAutoConfiguration {
@Configuration @Configuration
@ConditionalOnClass(PeerAwareInstanceRegistry.class) @ConditionalOnClass(PeerAwareInstanceRegistry.class)
@ConditionalOnBean(EurekaInstanceConfig.class)
protected static class Initializer implements protected static class Initializer implements
ApplicationListener<EurekaRegistryAvailableEvent> { ApplicationListener<EurekaRegistryAvailableEvent> {

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2013-2014 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.cloud.cloudfoundry.oauth2;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author Dave Syer
*
*/
@ConfigurationProperties("oauth2.resource")
@Data
public class ResourceServerProperties {
private String serviceId = "resource";
private String id;
@Value("${vcap.services.${oauth2.resource.serviceId:resource}.credentials.clientId:}")
private String clientId;
@Value("${vcap.services.${oauth2.resource.serviceId:resource}.credentials.clientSecret:}")
private String clientSecret;
@Value("${vcap.services.${oauth2.resource.serviceId:sso}.credentials.userInfoUri:}")
private String userInfoUri;
@Value("${vcap.services.${oauth2.resource.serviceId:sso}.credentials.tokenInfoUri:}")
private String tokenInfoUri;
private boolean preferTokenInfo = true;
public String getResourceId() {
return id==null ? clientId : id;
}
}

View File

@@ -0,0 +1,108 @@
/*
* Copyright 2013-2014 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.cloud.cloudfoundry.oauth2;
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.ConditionalOnMissingClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.OAuth2RestOperations;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.social.connect.support.OAuth2ConnectionFactory;
/**
* @author Dave Syer
*
*/
@Configuration
@EnableConfigurationProperties(ResourceServerProperties.class)
public class ResourceServerTokenServicesConfiguration {
@Autowired
private ResourceServerProperties resource;
@Bean
@ConditionalOnMissingBean(ResourceServerTokenServices.class)
@ConditionalOnExpression("${oauth2.resource.preferTokenInfo:${OAUTH2_RESOURCE_PREFERTOKENINFO:true}}")
protected RemoteTokenServices remoteTokenServices() {
RemoteTokenServices services = new RemoteTokenServices();
services.setCheckTokenEndpointUrl(resource.getTokenInfoUri());
services.setClientId(resource.getClientId());
services.setClientSecret(resource.getClientSecret());
return services;
}
@Configuration
@ConditionalOnClass(OAuth2ConnectionFactory.class)
@ConditionalOnExpression("!${oauth2.resource.preferTokenInfo:${OAUTH2_RESOURCE_PREFERTOKENINFO:true}}")
protected static class SocialTokenServicesConfiguration {
@Autowired
private ResourceServerProperties sso;
@Autowired(required = false)
private OAuth2ConnectionFactory<?> connectionFactory;
@Autowired(required = false)
@Qualifier("oauth2RestTemplate")
private OAuth2RestOperations restTemplate;
@Bean
@ConditionalOnBean(OAuth2ConnectionFactory.class)
@ConditionalOnMissingBean(ResourceServerTokenServices.class)
public SpringSocialTokenServices socialTokenServices() {
return new SpringSocialTokenServices(connectionFactory, sso.getClientId());
}
@Bean
@ConditionalOnMissingBean({ OAuth2ConnectionFactory.class,
ResourceServerTokenServices.class })
public UserInfoTokenServices userInfoTokenServices() {
return new UserInfoTokenServices(restTemplate, sso.getUserInfoUri(),
sso.getClientId());
}
}
@Configuration
@ConditionalOnMissingClass(name = "org.springframework.social.connect.support.OAuth2ConnectionFactory")
@ConditionalOnExpression("!${oauth2.resource.preferTokenInfo:${OAUTH2_RESOURCE_PREFERTOKENINFO:true}}")
protected static class UserInfoTokenServicesConfiguration {
@Autowired
private ResourceServerProperties sso;
@Autowired
@Qualifier("oauth2RestTemplate")
private OAuth2RestOperations restTemplate;
@Bean
@ConditionalOnMissingBean(ResourceServerTokenServices.class)
public UserInfoTokenServices userInfoTokenServices() {
return new UserInfoTokenServices(restTemplate, sso.getUserInfoUri(),
sso.getClientId());
}
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2013-2014 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.cloud.cloudfoundry.oauth2;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.social.connect.Connection;
import org.springframework.social.connect.UserProfile;
import org.springframework.social.connect.support.OAuth2ConnectionFactory;
import org.springframework.social.oauth2.AccessGrant;
/**
* @author Dave Syer
*
*/
public class SpringSocialTokenServices implements ResourceServerTokenServices {
protected final Log logger = LogFactory.getLog(getClass());
private OAuth2ConnectionFactory<?> connectionFactory;
private String clientId;
public SpringSocialTokenServices(OAuth2ConnectionFactory<?> connectionFactory,
String clientId) {
this.connectionFactory = connectionFactory;
this.clientId = clientId;
}
@Override
public OAuth2Authentication loadAuthentication(String accessToken)
throws AuthenticationException, InvalidTokenException {
Connection<?> connection = connectionFactory.createConnection(new AccessGrant(
accessToken));
UserProfile user = connection.fetchUserProfile();
return extractAuthentication(user);
}
private OAuth2Authentication extractAuthentication(UserProfile user) {
UsernamePasswordAuthenticationToken principal = new UsernamePasswordAuthenticationToken(
user.getUsername(), "N/A",
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
principal.setDetails(user);
OAuth2Request request = new OAuth2Request(null, clientId, null, true, null, null,
null, null, null);
return new OAuth2Authentication(request, principal);
}
@Override
public OAuth2AccessToken readAccessToken(String accessToken) {
throw new UnsupportedOperationException("Not supported: read access token");
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2013-2014 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.cloud.cloudfoundry.oauth2;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.web.client.RestOperations;
/**
* @author Dave Syer
*
*/
public class UserInfoTokenServices implements ResourceServerTokenServices {
protected final Log logger = LogFactory.getLog(getClass());
private RestOperations restTemplate;
private String userInfoEndpointUrl;
private String clientId;
public UserInfoTokenServices(RestOperations restTemplate,
String userInfoEndpointUrl, String clientId) {
this.restTemplate = restTemplate;
this.userInfoEndpointUrl = userInfoEndpointUrl;
this.clientId = clientId;
}
@Override
public OAuth2Authentication loadAuthentication(String accessToken)
throws AuthenticationException, InvalidTokenException {
Map<String, Object> map = getMap(userInfoEndpointUrl);
if (map.containsKey("error")) {
logger.debug("userinfo returned error: " + map.get("error"));
throw new InvalidTokenException(accessToken);
}
return extractAuthentication(map);
}
private OAuth2Authentication extractAuthentication(Map<String, Object> map) {
UsernamePasswordAuthenticationToken user = new UsernamePasswordAuthenticationToken(
getPrincipal(map), "N/A",
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
user.setDetails(map);
OAuth2Request request = new OAuth2Request(null, clientId, null, true, null,
null, null, null, null);
return new OAuth2Authentication(request, user);
}
private Object getPrincipal(Map<String, Object> map) {
String[] keys = new String[] { "user", "username", "userid", "user_id",
"login", "id" };
for (String key : keys) {
if (map.containsKey(key)) {
return map.get(key);
}
}
return "unknown";
}
@Override
public OAuth2AccessToken readAccessToken(String accessToken) {
throw new UnsupportedOperationException("Not supported: read access token");
}
private Map<String, Object> getMap(String path) {
@SuppressWarnings("rawtypes")
Map map = restTemplate.getForEntity(path, Map.class).getBody();
@SuppressWarnings("unchecked")
Map<String, Object> result = map;
return result;
}
}

View File

@@ -31,11 +31,11 @@ import com.netflix.zuul.ZuulFilter;
@Configuration @Configuration
@ConditionalOnClass({ ZuulFilter.class, EnableOAuth2Client.class, SecurityProperties.class }) @ConditionalOnClass({ ZuulFilter.class, EnableOAuth2Client.class, SecurityProperties.class })
@ConditionalOnWebApplication @ConditionalOnWebApplication
public class CloudfoundryProxyConfiguration { public class OAuth2ProxyAutoConfiguration {
@Bean @Bean
public CloudfoundryTokenFilter cloudfoundryTokenFilter() { public OAuth2TokenFilter cloudfoundryTokenFilter() {
return new CloudfoundryTokenFilter(); return new OAuth2TokenFilter();
} }
} }

View File

@@ -8,7 +8,7 @@ import org.springframework.security.oauth2.provider.authentication.OAuth2Authent
import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.context.RequestContext;
public class CloudfoundryTokenFilter extends ZuulFilter { public class OAuth2TokenFilter extends ZuulFilter {
private static final String ACCESS_TOKEN = "ACCESS_TOKEN"; private static final String ACCESS_TOKEN = "ACCESS_TOKEN";

View File

@@ -1,69 +0,0 @@
/*
* Copyright 2013-2014 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.cloud.cloudfoundry.resource;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.StringUtils;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
/**
* @author Dave Syer
*
*/
@ConfigurationProperties("cloudfoundry.resource")
@Data
public class CloudfoundryResourceProperties implements Validator {
private String serviceId = "resource";
private String id;
@Value("${vcap.services.${cloudfoundry.resource.serviceId:resource}.credentials.clientId:}")
private String clientId;
@Value("${vcap.services.${cloudfoundry.resource.serviceId:resource}.credentials.clientSecret:}")
private String clientSecret;
@Value("${vcap.services.${cloudfoundry.resource.serviceId:resource}.credentials.tokenInfoUri:}")
private String tokenInfoUri;
@Override
public boolean supports(Class<?> clazz) {
return CloudfoundryResourceProperties.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
CloudfoundryResourceProperties resource = (CloudfoundryResourceProperties) target;
if (StringUtils.hasText(resource.getClientId())) {
if (!StringUtils.hasText(resource.getTokenInfoUri())) {
errors.rejectValue("tokenInfoUri", "missing.tokenInfoUri", "Missing tokenInfoUri");
}
if (!StringUtils.hasText(resource.getClientSecret())) {
errors.rejectValue("clientSecret", "missing.clientSecret", "Missing clientSecret");
}
}
}
public String getResourceId() {
return id==null ? clientId : id;
}
}

View File

@@ -30,7 +30,7 @@ import org.springframework.context.annotation.Import;
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Documented @Documented
@Import(CloudfoundryResourceConfiguration.class) @Import(OAuth2ResourceConfiguration.class)
public @interface EnableCloudfoundryResource { public @interface EnableOAuth2Resource {
} }

View File

@@ -24,17 +24,17 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.cloudfoundry.oauth2.ResourceServerProperties;
import org.springframework.cloud.cloudfoundry.oauth2.ResourceServerTokenServicesConfiguration;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfiguration; import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfiguration;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurer; import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
/** /**
@@ -42,40 +42,28 @@ import org.springframework.util.ClassUtils;
* *
*/ */
@Configuration @Configuration
@ConditionalOnExpression("'${cloudfoundry.resource.clientId:${vcap.services.resource.credentials.clientId:}}'!=''") @ConditionalOnExpression("'${oauth2.resource.clientId:${vcap.services.resource.credentials.clientId:}}'!=''")
@ConditionalOnClass({ EnableResourceServer.class, SecurityProperties.class }) @ConditionalOnClass({ EnableResourceServer.class, SecurityProperties.class })
@ConditionalOnWebApplication @ConditionalOnWebApplication
@EnableResourceServer @EnableResourceServer
@EnableConfigurationProperties(CloudfoundryResourceProperties.class) @Import(ResourceServerTokenServicesConfiguration.class)
public class CloudfoundryResourceConfiguration { public class OAuth2ResourceConfiguration {
@Autowired @Autowired
private CloudfoundryResourceProperties resource; private ResourceServerProperties resource;
@Bean @Bean
@ConditionalOnMissingBean(ResourceServerConfigurer.class) @ConditionalOnMissingBean(ResourceServerConfigurer.class)
public ResourceServerConfigurer resourceServer() { public ResourceServerConfigurer resourceServer() {
return new ResourceSecurityConfigurer(resource); return new ResourceSecurityConfigurer(resource);
} }
@Bean
@ConditionalOnMissingBean(ResourceServerTokenServices.class)
public ResourceServerTokenServices tokenServices() {
RemoteTokenServices services = new RemoteTokenServices();
services.setCheckTokenEndpointUrl(resource.getTokenInfoUri());
services.setClientId(resource.getClientId());
services.setClientSecret(resource.getClientSecret());
return services;
}
protected static class ResourceSecurityConfigurer extends ResourceServerConfigurerAdapter { protected static class ResourceSecurityConfigurer extends ResourceServerConfigurerAdapter {
private CloudfoundryResourceProperties resource; private ResourceServerProperties resource;
@Autowired @Autowired
public ResourceSecurityConfigurer(CloudfoundryResourceProperties resource) { public ResourceSecurityConfigurer(ResourceServerProperties resource) {
this.resource = resource; this.resource = resource;
} }

View File

@@ -21,8 +21,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
/** /**
* @author Dave Syer * @author Dave Syer
* *
@@ -30,7 +28,7 @@ import org.springframework.context.annotation.Import;
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Documented @Documented
@Import(CloudfoundrySsoConfiguration.class) @EnableOAuth2Sso
public @interface EnableCloudfoundrySso { public @interface EnableCloudfoundrySso {
} }

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2013-2014 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.cloud.cloudfoundry.sso;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
/**
* @author Dave Syer
*
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(OAuth2SsoConfiguration.class)
public @interface EnableOAuth2Sso {
}

View File

@@ -16,6 +16,7 @@
package org.springframework.cloud.cloudfoundry.sso; package org.springframework.cloud.cloudfoundry.sso;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@@ -32,11 +33,19 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat
import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.context.embedded.FilterRegistrationBean; import org.springframework.boot.context.embedded.FilterRegistrationBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.cloudfoundry.oauth2.ResourceServerTokenServicesConfiguration;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
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.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
@@ -49,30 +58,33 @@ import org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticat
import org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter; import org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter;
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.grant.code.AuthorizationCodeAccessTokenProvider;
import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails; import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.MultiValueMap;
/** /**
* @author Dave Syer * @author Dave Syer
* *
*/ */
@Configuration @Configuration
@ConditionalOnExpression("'${cloudfoundry.sso.clientId:${vcap.services.sso.credentials.clientId:}}'!=''") @ConditionalOnExpression("'${oauth2.sso.clientId:${vcap.services.sso.credentials.clientId:}}'!=''")
@ConditionalOnClass({ EnableOAuth2Client.class, SecurityProperties.class }) @ConditionalOnClass({ EnableOAuth2Client.class, SecurityProperties.class })
@ConditionalOnWebApplication @ConditionalOnWebApplication
@EnableOAuth2Client @EnableOAuth2Client
@EnableConfigurationProperties(CloudfoundrySsoProperties.class) @EnableConfigurationProperties(OAuth2SsoProperties.class)
public class CloudfoundrySsoConfiguration { @Import(ResourceServerTokenServicesConfiguration.class)
public class OAuth2SsoConfiguration {
@Autowired @Autowired
private CloudfoundrySsoProperties sso; private OAuth2SsoProperties sso;
@Resource @Resource
@Qualifier("accessTokenRequest") @Qualifier("accessTokenRequest")
@@ -88,19 +100,42 @@ public class CloudfoundrySsoConfiguration {
} }
@Bean @Bean
public OAuth2ProtectedResourceDetails remote() { public OAuth2ProtectedResourceDetails oauth2RemoteResource() {
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails(); AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
// set up resource details, OAuth2 URLs etc. // set up resource details, OAuth2 URLs etc.
details.setClientId(sso.getClientId()); details.setClientId(sso.getClientId());
details.setClientSecret(sso.getClientSecret()); details.setClientSecret(sso.getClientSecret());
details.setAccessTokenUri(sso.getTokenUri()); details.setAccessTokenUri(sso.getTokenUri());
details.setUserAuthorizationUri(sso.getAuthorizationUri()); details.setUserAuthorizationUri(sso.getAuthorizationUri());
details.setClientAuthenticationScheme(sso.getAuthenticationScheme());
return details; return details;
} }
@Bean @Bean
public OAuth2RestOperations restTemplate() { public OAuth2RestOperations oauth2RestTemplate() {
return new OAuth2RestTemplate(remote(), oauth2ClientContext()); 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;
} }
@Bean @Bean
@@ -117,12 +152,16 @@ public class CloudfoundrySsoConfiguration {
private OAuth2ProtectedResourceDetails remote; private OAuth2ProtectedResourceDetails remote;
@Autowired @Autowired
private CloudfoundrySsoProperties sso; private OAuth2SsoProperties sso;
@Autowired @Autowired
private ResourceServerTokenServices tokenServices;
@Autowired
@Qualifier("oauth2RestTemplate")
private OAuth2RestOperations restTemplate; private OAuth2RestOperations restTemplate;
private List<CloudfoundrySsoConfigurer> configurers = Collections.emptyList(); private List<OAuth2SsoConfigurer> configurers = Collections.emptyList();
@Override @Override
public int getOrder() { public int getOrder() {
@@ -139,7 +178,7 @@ public class CloudfoundrySsoConfiguration {
* @param configurers the configurers to set * @param configurers the configurers to set
*/ */
@Autowired(required = false) @Autowired(required = false)
public void setConfigurers(List<CloudfoundrySsoConfigurer> configurers) { public void setConfigurers(List<OAuth2SsoConfigurer> configurers) {
this.configurers = configurers; this.configurers = configurers;
} }
@@ -149,7 +188,7 @@ public class CloudfoundrySsoConfiguration {
http.addFilterAfter(cloudfoundrySsoFilter(), http.addFilterAfter(cloudfoundrySsoFilter(),
AbstractPreAuthenticatedProcessingFilter.class); AbstractPreAuthenticatedProcessingFilter.class);
for (CloudfoundrySsoConfigurer configurer : configurers) { for (OAuth2SsoConfigurer configurer : configurers) {
// Delegates can add authorizeRequests() here // Delegates can add authorizeRequests() here
configurer.configure(http); configurer.configure(http);
} }
@@ -165,7 +204,8 @@ public class CloudfoundrySsoConfiguration {
requests.anyRequest().authenticated(); requests.anyRequest().authenticated();
} }
http.logout().logoutRequestMatcher(new AntPathRequestMatcher(sso.getLogoutPath())) http.logout()
.logoutRequestMatcher(new AntPathRequestMatcher(sso.getLogoutPath()))
.addLogoutHandler(logoutHandler()); .addLogoutHandler(logoutHandler());
http.exceptionHandling().authenticationEntryPoint( http.exceptionHandling().authenticationEntryPoint(
new LoginUrlAuthenticationEntryPoint(sso.getLoginPath())); new LoginUrlAuthenticationEntryPoint(sso.getLoginPath()));
@@ -176,18 +216,10 @@ public class CloudfoundrySsoConfiguration {
OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter( OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter(
sso.getLoginPath()); sso.getLoginPath());
filter.setRestTemplate(restTemplate); filter.setRestTemplate(restTemplate);
filter.setTokenServices(tokenServices()); filter.setTokenServices(tokenServices);
return filter; return filter;
} }
private ResourceServerTokenServices tokenServices() {
RemoteTokenServices services = new RemoteTokenServices();
services.setCheckTokenEndpointUrl(sso.getTokenInfoUri());
services.setClientId(sso.getClientId());
services.setClientSecret(sso.getClientSecret());
return services;
}
private LogoutHandler logoutHandler() { private LogoutHandler logoutHandler() {
LogoutHandler handler = new LogoutHandler() { LogoutHandler handler = new LogoutHandler() {
@Override @Override

View File

@@ -21,7 +21,7 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
* @author Dave Syer * @author Dave Syer
* *
*/ */
public interface CloudfoundrySsoConfigurer { public interface OAuth2SsoConfigurer {
void configure(HttpSecurity http); void configure(HttpSecurity http);

View File

@@ -21,7 +21,7 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
* @author Dave Syer * @author Dave Syer
* *
*/ */
public class CloudfoundrySsoConfigurerAdapter implements CloudfoundrySsoConfigurer { public class OAuth2SsoConfigurerAdapter implements OAuth2SsoConfigurer {
@Override @Override
public void configure(HttpSecurity http) { public void configure(HttpSecurity http) {

View File

@@ -19,6 +19,7 @@ import lombok.Data;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.security.oauth2.common.AuthenticationScheme;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.validation.Errors; import org.springframework.validation.Errors;
import org.springframework.validation.Validator; import org.springframework.validation.Validator;
@@ -27,34 +28,33 @@ import org.springframework.validation.Validator;
* @author Dave Syer * @author Dave Syer
* *
*/ */
@ConfigurationProperties("cloudfoundry.sso") @ConfigurationProperties("oauth2.sso")
@Data @Data
public class CloudfoundrySsoProperties implements Validator { public class OAuth2SsoProperties implements Validator {
private String serviceId = "sso"; private String serviceId = "sso";
private String logoutPath = "/logout"; private String logoutPath = "/logout";
@Value("${vcap.services.${oauth2.sso.serviceId:sso}.credentials.logoutUri:}")
private String logoutUri;
private String loginPath = "/login"; private String loginPath = "/login";
@Value("${vcap.services.${cloudfoundry.sso.serviceId:sso}.credentials.tokenUri:}") @Value("${vcap.services.${oauth2.sso.serviceId:sso}.credentials.tokenUri:}")
private String tokenUri; private String tokenUri;
@Value("${vcap.services.${cloudfoundry.sso.serviceId:sso}.credentials.userInfoUri:}") @Value("${vcap.services.${oauth2.sso.serviceId:sso}.credentials.authorizationUri:}")
private String userInfoUri;
@Value("${vcap.services.${cloudfoundry.sso.serviceId:sso}.credentials.tokenInfoUri:}")
private String tokenInfoUri;
@Value("${vcap.services.${cloudfoundry.sso.serviceId:sso}.credentials.authorizationUri:}")
private String authorizationUri; private String authorizationUri;
@Value("${vcap.services.${cloudfoundry.sso.serviceId:sso}.credentials.clientId:}") @Value("${vcap.services.${oauth2.sso.serviceId:sso}.credentials.clientId:}")
private String clientId; private String clientId;
@Value("${vcap.services.${cloudfoundry.sso.serviceId:sso}.credentials.clientSecret:}") @Value("${vcap.services.${oauth2.sso.serviceId:sso}.credentials.clientSecret:}")
private String clientSecret; private String clientSecret;
private AuthenticationScheme authenticationScheme = AuthenticationScheme.header;
private Home home = new Home(); private Home home = new Home();
@Data @Data
@@ -63,27 +63,19 @@ public class CloudfoundrySsoProperties implements Validator {
private boolean secure = true; private boolean secure = true;
} }
public String getTokenInfoUri() {
return StringUtils.hasText(tokenInfoUri) ? tokenInfoUri : tokenUri.replace(
"/oauth/token", "/check_token");
}
public String getUserInfoUri() {
return tokenUri.replace("/oauth/token", "/user_info");
}
public String getLogoutUri(String redirectUrl) { public String getLogoutUri(String redirectUrl) {
return tokenUri.replace("/oauth/token", "/logout.do?redirect=" + redirectUrl); return logoutUri != null ? logoutUri : tokenUri.replace("/oauth/token",
"/logout.do?redirect=" + redirectUrl);
} }
@Override @Override
public boolean supports(Class<?> clazz) { public boolean supports(Class<?> clazz) {
return CloudfoundrySsoProperties.class.isAssignableFrom(clazz); return OAuth2SsoProperties.class.isAssignableFrom(clazz);
} }
@Override @Override
public void validate(Object target, Errors errors) { public void validate(Object target, Errors errors) {
CloudfoundrySsoProperties sso = (CloudfoundrySsoProperties) target; OAuth2SsoProperties sso = (OAuth2SsoProperties) target;
if (StringUtils.hasText(sso.getClientId())) { if (StringUtils.hasText(sso.getClientId())) {
if (!StringUtils.hasText(sso.getAuthorizationUri())) { if (!StringUtils.hasText(sso.getAuthorizationUri())) {
errors.rejectValue("authorizeUri", "missing.authorizeUri", errors.rejectValue("authorizeUri", "missing.authorizeUri",

View File

@@ -1,3 +1,3 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.cloudfoundry.broker.configuration.ServiceBrokerAutoConfiguration,\ org.springframework.cloud.cloudfoundry.broker.configuration.ServiceBrokerAutoConfiguration,\
org.springframework.cloud.cloudfoundry.proxy.CloudfoundryProxyConfiguration org.springframework.cloud.cloudfoundry.proxy.OAuth2ProxyAutoConfiguration

View File

@@ -15,15 +15,25 @@
*/ */
package org.springframework.cloud.cloudfoundry.broker.sample; package org.springframework.cloud.cloudfoundry.broker.sample;
import java.security.Principal;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.cloud.cloudfoundry.sso.EnableCloudfoundrySso; import org.springframework.cloud.cloudfoundry.sso.EnableOAuth2Sso;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Configuration @Configuration
@EnableAutoConfiguration @EnableAutoConfiguration
@EnableCloudfoundrySso @EnableOAuth2Sso
@RestController
public class Application { public class Application {
@RequestMapping("/")
public Principal home(Principal principal) {
return principal;
}
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(Application.class, args); SpringApplication.run(Application.class, args);

View File

@@ -1,12 +1,15 @@
debug: true debug: true
server: server:
port: 8787 port: 8080
spring: spring:
application: application:
name: eureka name: eureka
management: management:
context-path: /admin context-path: /admin
cloudfoundry: eureka:
server:
enabled: false
oauth2:
sso: sso:
tokenUri: http://localhost:8080/uaa/oauth/token tokenUri: http://localhost:8080/uaa/oauth/token
authorizationUri: http://localhost:8080/uaa/oauth/authorize authorizationUri: http://localhost:8080/uaa/oauth/authorize
@@ -16,12 +19,19 @@ logging:
level: level:
com.netflix.discovery: 'OFF' com.netflix.discovery: 'OFF'
org.springframework.security: DEBUG org.springframework.security: DEBUG
eureka:
server: ---
waitTimeInMsWhenSyncEmpty: 1000 spring:
client: profiles: github
serviceUrl: oauth2:
defaultZone: http://localhost:8787/v2/ sso:
default.defaultZone: http://localhost:8787/v2/ tokenUri: https://github.com/login/oauth/access_token
registerWithEureka: false authorizationUri: https://github.com/login/oauth/authorize
fetchRegistry: false clientId: bd1c0a783ccdd1c9b9e4
clientSecret: 1a9030fbca47a5b2c28e92f19050bb77824b5ad1
authenticationScheme: form
resource:
clientId: ${oauth2.sso.clientId}
userInfoUri: https://api.github.com/user
preferTokenInfo: false