diff --git a/README.adoc b/README.adoc
index e7b01f9..2c719f5 100644
--- a/README.adoc
+++ b/README.adoc
@@ -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).
\ No newline at end of file
+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.
\ No newline at end of file
diff --git a/docs/src/main/asciidoc/intro.adoc b/docs/src/main/asciidoc/intro.adoc
index a910fdb..23046cf 100644
--- a/docs/src/main/asciidoc/intro.adoc
+++ b/docs/src/main/asciidoc/intro.adoc
@@ -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)).
\ No newline at end of file
diff --git a/docs/src/main/asciidoc/spring-cloud-cloudfoundry.adoc b/docs/src/main/asciidoc/spring-cloud-cloudfoundry.adoc
index 01eaa77..3c2f170 100644
--- a/docs/src/main/asciidoc/spring-cloud-cloudfoundry.adoc
+++ b/docs/src/main/asciidoc/spring-cloud-cloudfoundry.adoc
@@ -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`.
+
diff --git a/spring-cloud-cloudfoundry-discovery/src/test/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryAutoConfigurationTest.java b/spring-cloud-cloudfoundry-discovery/src/test/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryAutoConfigurationTest.java
index 80dfc99..9cc144c 100644
--- a/spring-cloud-cloudfoundry-discovery/src/test/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryAutoConfigurationTest.java
+++ b/spring-cloud-cloudfoundry-discovery/src/test/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryAutoConfigurationTest.java
@@ -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 Josh Long
*/
@@ -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();
}
diff --git a/spring-cloud-cloudfoundry-web/src/main/java/org/springframework/cloud/cloudfoundry/environment/VcapServiceCredentialsEnvironmentPostProcessor.java b/spring-cloud-cloudfoundry-web/src/main/java/org/springframework/cloud/cloudfoundry/environment/VcapServiceCredentialsEnvironmentPostProcessor.java
new file mode 100644
index 0000000..f4ade56
--- /dev/null
+++ b/spring-cloud-cloudfoundry-web/src/main/java/org/springframework/cloud/cloudfoundry/environment/VcapServiceCredentialsEnvironmentPostProcessor.java
@@ -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 properties = resolver.getSubProperties("vcap.services.");
+ if (properties == null || properties.isEmpty()) {
+ return;
+ }
+ Map source = new HashMap();
+ 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 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),
+ "");
+ }
+
+}
diff --git a/spring-cloud-cloudfoundry-web/src/test/java/org/springframework/cloud/cloudfoundry/environment/VcapServiceCredentialsEnvironmentPostProcessorTests.java b/spring-cloud-cloudfoundry-web/src/test/java/org/springframework/cloud/cloudfoundry/environment/VcapServiceCredentialsEnvironmentPostProcessorTests.java
new file mode 100644
index 0000000..f89bd7c
--- /dev/null
+++ b/spring-cloud-cloudfoundry-web/src/test/java/org/springframework/cloud/cloudfoundry/environment/VcapServiceCredentialsEnvironmentPostProcessorTests.java
@@ -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 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}"));
+ }
+
+}