Move VCAP_SERVICES processing over from spring-cloud-security

This commit is contained in:
Dave Syer
2015-08-27 10:18:06 +01:00
parent 4437822fec
commit 5a8930af9d
6 changed files with 252 additions and 13 deletions

View File

@@ -8,10 +8,17 @@ middlware that you "bind" to an app, essentially providing it with an
environment variable containing credentials (e.g. the location and
username to use for the service).
Add this project as a dependency to any Spring Cloud UI app or REST
service and deploy to Cloudfoundry. If you use Spring Cloud Security
OAuth2 features this will make them bindable to Cloud Foundry services
instead of enironment properties in `spring.oauth2.*`. For a UI app you can
declare `@EnableOAuth2Sso` and bind to a service called "sso", and for
a service you can add `@EnableOAuth2Resource` and bind to a service
called "resource" (see below for how to change the names).
The `spring-cloud-cloudfoundry-web` project provides basic support for
some enhanced features of webapps in Cloud Foundry: binding
automatically to single-sign-on services and optionally enabling
sticky routing for discovery.
The `spring-cloud-cloudfoundry-discovery` project provides an
implementation of Spring Cloud Commons `DiscoveryClient` so you can
`@EnableDiscoveryClient` and provide your credentials as
`spring.cloud.cloudfoundry.discovery.[email,password]` and then you
can use the `DiscoveryClient` directly or via a `LoadBalancerClient`
(also `*.url` if you are not connecting to [Pivotal Web
Services](https://run.pivotal.io)).
> NOTE: if you are looking for a way to bind to services then this is the wrong library. Check out the [Spring Cloud Connectors](https://github.com/spring-cloud/spring-cloud-connectors) instead.

View File

@@ -6,7 +6,15 @@ middlware that you "bind" to an app, essentially providing it with an
environment variable containing credentials (e.g. the location and
username to use for the service).
This project provides an implementation of Spring Cloud Commons `DiscoveryClient` so you can `@EnableDiscoveryClient`
and provide your credentials as `spring.cloud.cloudfoundry.discovery.[email,password]` and then you can use the
`DiscoveryClient` directly or via a `LoadBalancerClient` (also `*.url` if you are not connecting to [Pivotal Web
The `spring-cloud-cloudfoundry-web` project provides basic support for
some enhanced features of webapps in Cloud Foundry: binding
automatically to single-sign-on services and optionally enabling
sticky routing for discovery.
The `spring-cloud-cloudfoundry-discovery` project provides an
implementation of Spring Cloud Commons `DiscoveryClient` so you can
`@EnableDiscoveryClient` and provide your credentials as
`spring.cloud.cloudfoundry.discovery.[email,password]` and then you
can use the `DiscoveryClient` directly or via a `LoadBalancerClient`
(also `*.url` if you are not connecting to [Pivotal Web
Services](https://run.pivotal.io)).

View File

@@ -9,3 +9,17 @@ the fact that it has to get an access token from Cloud Foundry.
include::quickstart.adoc[]
=== Single Sign On
NOTE: All of the OAuth2 SSO and resource server features moved to Spring Boot
in version 1.3. You can find documentation in the
http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/[Spring Boot user guide].
This project provides automatic binding from CloudFoundry service
credentials to the Spring Boot features. If you have a CloudFoundry
service called "sso", for instance, with credentials containing
"client_id", "client_secret" and "auth_domain", it will bind
automatically to the Spring OAuth2 client that you enable with
`@EnableOAuth2Sso` (from Spring Boot). The name of the service can be
parameterized using `spring.oauth2.sso.serviceId`.

View File

@@ -32,8 +32,6 @@ import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Collections;
/**
* @author <A href= "josh@joshlong.com">Josh Long</A>
*/
@@ -60,7 +58,7 @@ public class CloudFoundryAutoConfigurationTest {
+ "\"state_timestamp\":1431028810}";
this.context = new SpringApplicationBuilder()
.properties(Collections.singletonMap("VCAP_APPLICATION", vcapAppl))
.properties("VCAP_APPLICATION:"+vcapAppl, "server.port=0")
.sources(SimpleConfiguration.class).run();
}

View File

@@ -0,0 +1,118 @@
/*
* 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.environment;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.boot.context.config.ConfigFileEnvironmentPostProcessor;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.util.StringUtils;
/**
* @author Dave Syer
*
*/
public class VcapServiceCredentialsEnvironmentPostProcessor
implements EnvironmentPostProcessor, Ordered {
// After VcapEnvironmentPostProcessor and ConfigFileEnvironmentPostProcessor so
// values here can
// use those ones
private int order = ConfigFileEnvironmentPostProcessor.DEFAULT_ORDER + 1;
@Override
public int getOrder() {
return this.order;
}
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(environment);
Map<String, Object> properties = resolver.getSubProperties("vcap.services.");
if (properties == null || properties.isEmpty()) {
return;
}
Map<String, Object> source = new HashMap<String, Object>();
String serviceId;
if (!resolver.getSubProperties("security.oauth2.resource.").isEmpty()) {
serviceId = resolver.getProperty("security.oauth2.resource.serviceId",
"resource");
}
else {
serviceId = resolver.getProperty("security.oauth2.sso.serviceId", "sso");
}
String authDomain = (String) properties
.get(serviceId + ".credentials.auth_domain");
if (authDomain != null) {
source.put("security.oauth2.resource.userInfoUri",
authDomain + "/userinfo");
source.put("security.oauth2.resource.keyUri", authDomain + "/token_key");
source.put("security.oauth2.resource.accessTokenUri",
authDomain + "/oauth/token");
source.put("security.oauth2.resource.authorizationUri",
authDomain + "/oauth/authorization");
}
else {
addProperty(source, resolver, serviceId, "resource", "userInfoUri");
addProperty(source, resolver, serviceId, "resource", "tokenInfoUri");
addProperty(source, resolver, serviceId, "resource", "keyUri");
addProperty(source, resolver, serviceId, "resource", "keyValue");
addProperty(source, resolver, serviceId, "client", "accessTokenUri", "tokenUri");
addProperty(source, resolver, serviceId, "client", "authorizationUri");
}
addProperty(source, resolver, serviceId, "client", "clientId");
addProperty(source, resolver, serviceId, "client", "clientSecret");
addProperty(source, resolver, serviceId, "client", "scope");
String resourceId = resolver
.getProperty("vcap.services." + serviceId + ".credentials.id", "");
if (StringUtils.hasText(resourceId)) {
source.put("security.oauth2.resource.id", resourceId);
}
environment.getPropertySources()
.addLast(new MapPropertySource("cloudDefaultSecurityBindings", source));
}
private void addProperty(Map<String, Object> source,
RelaxedPropertyResolver resolver, String serviceId, String stem, String key, String... altKeys) {
String value = resolve(resolver, serviceId, key);
if (StringUtils.hasText(value)) {
source.put("security.oauth2."+stem+"." + key, value);
return;
}
for (String altKey : altKeys) {
value = resolve(resolver, serviceId, altKey);
if (StringUtils.hasText(value)) {
source.put("security.oauth2."+stem+"." + key, value);
return;
}
}
}
private String resolve(RelaxedPropertyResolver resolver, String serviceId,
String key) {
return resolver.getProperty(
String.format("vcap.services.%s.credentials.%s", serviceId, key),
"");
}
}

View File

@@ -0,0 +1,94 @@
/*
* 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.environment;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.Map;
import org.junit.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.StandardEnvironment;
/**
* @author Dave Syer
*
*/
public class VcapServiceCredentialsEnvironmentPostProcessorTests {
private VcapServiceCredentialsEnvironmentPostProcessor listener = new VcapServiceCredentialsEnvironmentPostProcessor();
private ConfigurableEnvironment environment = new StandardEnvironment();
@Test
public void noop() {
this.listener.postProcessEnvironment(this.environment, new SpringApplication());
Map<String, Object> properties = new RelaxedPropertyResolver(this.environment)
.getSubProperties("security.oauth2");
assertTrue(properties == null || properties.isEmpty());
}
@Test
public void addClientId() {
EnvironmentTestUtils.addEnvironment(this.environment,
"vcap.services.sso.credentials.clientId:foo");
this.listener.postProcessEnvironment(this.environment, new SpringApplication());
assertEquals("foo", this.environment
.resolvePlaceholders("${security.oauth2.client.clientId}"));
}
@Test
public void addClientIdUnderscores() {
EnvironmentTestUtils.addEnvironment(this.environment,
"vcap.services.sso.credentials.client_id:foo");
this.listener.postProcessEnvironment(this.environment, new SpringApplication());
assertEquals("foo", this.environment
.resolvePlaceholders("${security.oauth2.client.clientId}"));
}
@Test
public void addTokenUri() {
EnvironmentTestUtils.addEnvironment(this.environment,
"vcap.services.sso.credentials.accessTokenUri:http://example.com");
this.listener.postProcessEnvironment(this.environment, new SpringApplication());
assertEquals("http://example.com", this.environment
.resolvePlaceholders("${security.oauth2.client.accessTokenUri}"));
}
@Test
public void addUserInfoUri() {
EnvironmentTestUtils.addEnvironment(this.environment,
"vcap.services.sso.credentials.userInfoUri:http://example.com");
this.listener.postProcessEnvironment(this.environment, new SpringApplication());
assertEquals("http://example.com", this.environment
.resolvePlaceholders("${security.oauth2.resource.userInfoUri}"));
}
@Test
public void addServiceId() {
EnvironmentTestUtils.addEnvironment(this.environment,
"vcap.services.my.credentials.tokenUri:http://example.com",
"security.oauth2.sso.serviceId:my");
this.listener.postProcessEnvironment(this.environment, new SpringApplication());
assertEquals("http://example.com", this.environment
.resolvePlaceholders("${security.oauth2.client.accessTokenUri}"));
}
}