Repository: SAP/cloud-security-xsuaa-integration
Pull request #91 (2019-06-14 17:06:52)

Introduce Spring Boot Auto Configuration
### Changes 
- introduces basic autoconfig for `XsuaaServiceConfiguration`
- introduces autoconfig for `JwtDecoder`
- `XsuaaJwtDecoderBuilder` can be configured with default validators 
https://github.com/SAP/cloud-security-xsuaa-integration/blob/a31768d497d65de0551d35d0491ef1549b6f2134/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaJwtDecoderBuilder.java#L81
- uses default validators from spring-security Oauth2 https://github.com/SAP/cloud-security-xsuaa-integration/blob/a31768d497d65de0551d35d0491ef1549b6f2134/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaJwtDecoderBuilder.java#L27

### Open Todos / follow ups
- [x] update documentation
- [x] update integration tests and samples
- [x] apply code formatting rules
- [ ] enhance changelog and increase version
- [x] some further tests with existing sample applications
- [x] issue #86

nenaraab: @mwdb this is done from my side. 
Version and changelog needs to be updated, when it is merged into master.
nenaraab: have created another one, which should be merged into version1.5 branch

Patch:
diff --git a/README.md b/README.md
index cf28204e..a0642788 100644
--- a/README.md
+++ b/README.md
@@ -41,13 +41,13 @@ See [spring-security-xsuaa-usage](samples/spring-security-xsuaa-usage) for an ex
 # Download and Installation
 Build results are published to maven central: https://search.maven.org/search?q=com.sap.cloud.security 
 
-To download and install the this project clone this repository via:
+To download and install this project clone this repository via:
 ```
 git clone https://github.com/SAP/cloud-security-xsuaa-integration
 cd cloud-security-xsuaa-integration
-mvn package
+mvn clean install
 ```
-*Note:* Use this is you want to enhance this xsuaa integration libraries. The buid results are also available on maven central. 
+*Note:* Use this if you want to enhance this xsuaa integration libraries. The build results are also available on maven central.
 
 # Limitations
 Libraries and information provided here is around the topic of integrating with the xsuaa service. General integration into other OAuth authorization servers is not the primary focus.
diff --git a/pom.xml b/pom.xml
index 20f77581..1c4dd92d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -52,6 +52,7 @@
 		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 		<spring.core.version>5.1.1.RELEASE</spring.core.version>
+		<spring.boot.version>2.1.5.RELEASE</spring.boot.version>
 		<junit.version>4.12</junit.version>
 		<maven.compiler.target>1.8</maven.compiler.target>
 		<maven.compiler.source>1.8</maven.compiler.source>
diff --git a/samples/spring-security-basic-auth/README.md b/samples/spring-security-basic-auth/README.md
index 19f02778..2c16aabd 100644
--- a/samples/spring-security-basic-auth/README.md
+++ b/samples/spring-security-basic-auth/README.md
@@ -20,7 +20,6 @@ Configure the OAuth resource server by:
 ```java
 @EnableWebSecurity
 @EnableCaching
-@PropertySource(factory = XsuaaServicePropertySourceFactory.class, value = { "" })
 public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
 
    @Autowired
@@ -52,15 +51,6 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
         return converter;
    }
 
-   @Bean
-   JwtDecoder jwtDecoder() {
-        return new XsuaaJwtDecoderBuilder(xsuaaServiceConfiguration).build();
-   }
-
-   @Bean
-   XsuaaServiceConfigurationDefault xsuaaConfig() {
-        return new XsuaaServiceConfigurationDefault();
-   }
 }
 ```
 
diff --git a/samples/spring-security-basic-auth/src/main/java/sample/spring/xsuaa/SecurityConfiguration.java b/samples/spring-security-basic-auth/src/main/java/sample/spring/xsuaa/SecurityConfiguration.java
index 84ea8af2..80526760 100644
--- a/samples/spring-security-basic-auth/src/main/java/sample/spring/xsuaa/SecurityConfiguration.java
+++ b/samples/spring-security-basic-auth/src/main/java/sample/spring/xsuaa/SecurityConfiguration.java
@@ -19,7 +19,6 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.cache.CacheManager;
 import org.springframework.cache.annotation.EnableCaching;
 import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.PropertySource;
 import org.springframework.core.convert.converter.Converter;
 import org.springframework.security.authentication.AbstractAuthenticationToken;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@@ -27,18 +26,14 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
 import org.springframework.security.config.http.SessionCreationPolicy;
 import org.springframework.security.oauth2.jwt.Jwt;
-import org.springframework.security.oauth2.jwt.JwtDecoder;
 
 import com.sap.cloud.security.xsuaa.XsuaaServiceConfigurationDefault;
-import com.sap.cloud.security.xsuaa.XsuaaServicePropertySourceFactory;
 import com.sap.cloud.security.xsuaa.extractor.AuthenticationMethod;
 import com.sap.cloud.security.xsuaa.extractor.TokenBrokerResolver;
 import com.sap.cloud.security.xsuaa.token.TokenAuthenticationConverter;
-import com.sap.cloud.security.xsuaa.token.authentication.XsuaaJwtDecoderBuilder;
 
 @EnableWebSecurity
 @EnableCaching
-@PropertySource(factory = XsuaaServicePropertySourceFactory.class, value = { "" })
 public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
 
 	@Autowired
@@ -73,14 +68,4 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
 		return converter;
 	}
 
-
-	@Bean
-	JwtDecoder jwtDecoder() {
-		return new XsuaaJwtDecoderBuilder(xsuaaServiceConfiguration).build();
-	}
-
-	@Bean
-	XsuaaServiceConfigurationDefault xsuaaConfig() {
-		return new XsuaaServiceConfigurationDefault();
-	}
 }
diff --git a/samples/spring-security-xsuaa-usage/src/main/java/sample/spring/xsuaa/SecurityConfiguration.java b/samples/spring-security-xsuaa-usage/src/main/java/sample/spring/xsuaa/SecurityConfiguration.java
index e711bb5a..92cfa3a6 100644
--- a/samples/spring-security-xsuaa-usage/src/main/java/sample/spring/xsuaa/SecurityConfiguration.java
+++ b/samples/spring-security-xsuaa-usage/src/main/java/sample/spring/xsuaa/SecurityConfiguration.java
@@ -17,9 +17,7 @@ package sample.spring.xsuaa;
 
 import com.sap.cloud.security.xsuaa.XsuaaServiceConfiguration;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.PropertySource;
 import org.springframework.core.convert.converter.Converter;
 import org.springframework.security.authentication.AbstractAuthenticationToken;
 import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
@@ -28,17 +26,12 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
 import org.springframework.security.config.http.SessionCreationPolicy;
 import org.springframework.security.oauth2.jwt.Jwt;
-import org.springframework.security.oauth2.jwt.JwtDecoder;
 
-import com.sap.cloud.security.xsuaa.XsuaaServiceConfigurationDefault;
-import com.sap.cloud.security.xsuaa.XsuaaServicePropertySourceFactory;
 import com.sap.cloud.security.xsuaa.token.TokenAuthenticationConverter;
-import com.sap.cloud.security.xsuaa.token.authentication.XsuaaJwtDecoderBuilder;
 
 @Configuration
-@EnableWebSecurity(debug = true) // TODO This may include sensitive information. Do not use in a production system!
+@EnableWebSecurity(debug = true) // TODO "debug" may include sensitive information. Do not use in a production system!
 @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
-@PropertySource(factory = XsuaaServicePropertySourceFactory.class, value = { "" })
 public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
 
 	@Autowired
@@ -71,13 +64,4 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
 		return converter;
 	}
 
-	@Bean
-	JwtDecoder jwtDecoder() {
-		return new XsuaaJwtDecoderBuilder(xsuaaServiceConfiguration).build();
-	}
-
-	@Bean
-	XsuaaServiceConfigurationDefault config() {
-		return new XsuaaServiceConfigurationDefault();
-	}
 }
diff --git a/spring-xsuaa/README.md b/spring-xsuaa/README.md
index 4a132493..85dee42d 100644
--- a/spring-xsuaa/README.md
+++ b/spring-xsuaa/README.md
@@ -27,6 +27,16 @@ This library enhances the [spring-security](https://github.com/spring-projects/s
 </dependency>
 ```
 
+### Auto-configuration
+The Xsuaa integration libraries auto-configures beans, that are required to initialize the Spring Boot application as OAuth resource server.
+
+Auto-configuration class | Description
+---- | --------
+XsuaaAutoConfiguration | Adds `xsuaa.*` properties to Spring's Environment. The properties are by default parsed from `VCAP_SERVICES` system environment variables and can be overwritten by properties such as `xsuaa.xsappname` e.g. for testing purposes. Furthermore it exposes a `XsuaaServiceConfiguration` bean that can be used to access xsuaa service information.  Alternatively you can access them with `@Value` annotation e.g. `@Value("${xsuaa.xsappname:}") String appId`.
+XsuaaResourceServerJwkAutoConfiguration | Configures a `JwtDecoder` bean with a JWK (JSON Web Keys) endpoint from where to download the tenant (subdomain) specific public key.
+
+You can gradually replace auto-configurations as explained [here](https://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-auto-configuration.html).
+
 
 ### Setup Security Context for HTTP requests
 Configure the OAuth resource server
@@ -34,7 +44,6 @@ Configure the OAuth resource server
 ```java
 @Configuration
 @EnableWebSecurity
-@PropertySource(factory = XsuaaServicePropertySourceFactory.class, value = {""})
 public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
     
     @Autowired
@@ -63,48 +72,13 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
         return converter;
     }
 
-    @Bean
-    JwtDecoder jwtDecoder() {
-        return new XsuaaJwtDecoderBuilder(xsuaaServiceConfiguration).build();
-    }
-
-    @Bean
-    XsuaaServiceConfigurationDefault xsuaaConfig() {
-        return new XsuaaServiceConfigurationDefault();
-    }
 }
 ```
 
-> Note: with `XsuaaServicePropertySourceFactory` the VCAP_SERVICES properties are read from the system environment variable and mapped to properties such as `xsuaa.xsappname`.
-> You can access them via Spring `@Value` annotation e.g. `@Value("${xsuaa.xsappname:}") String appId`.
-> For testing purposes you can overwrite them, for example, as part of a *.properties file.
 
 ### Setup Security Context for non-HTTP requests
-In case of non-HTTP requests, you may need to initialize the Spring `SecurityContext` with a JWT token you've received from a message / event or you've requested from XSUAA directly.
-
-Configure the `JwtDecoder` bean using the `XsuaaJwtDecoderBuilder` class
-
-```
-@Configuration
-@PropertySource(factory = XsuaaServicePropertySourceFactory.class, value = {""})
-public class SecurityConfiguration {
-
-    @Autowired
-    XsuaaServiceConfigurationDefault xsuaaServiceConfiguration;
-
-    @Bean
-    JwtDecoder jwtDecoder() {
-        return new XsuaaJwtDecoderBuilder(xsuaaServiceConfiguration).build();
-    }
-
-    @Bean
-    XsuaaServiceConfigurationDefault xsuaaConfig() {
-        return new XsuaaServiceConfigurationDefault();
-    }
-}
-```
+In case of non-HTTP requests, you may need to initialize the Spring `SecurityContext` with a JWT token you've received from a message / event or you've requested from XSUAA directly:
 
-Then, initialize the `SecurityContext`
 ```
 @Autowired
 JwtDecoder jwtDecoder;
diff --git a/spring-xsuaa/pom.xml b/spring-xsuaa/pom.xml
index f6e41426..e0e0c843 100644
--- a/spring-xsuaa/pom.xml
+++ b/spring-xsuaa/pom.xml
@@ -38,6 +38,12 @@
 			<version>${spring.security.version}</version>
 			<scope>provided</scope>
 		</dependency>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-autoconfigure</artifactId>
+			<version>${spring.boot.version}</version>
+			<scope>provided</scope>
+		</dependency>
 		<dependency>
 			<groupId>javax.servlet</groupId>
 			<artifactId>javax.servlet-api</artifactId>
@@ -61,6 +67,12 @@
 			<version>${junit.version}</version>
 			<scope>test</scope>
 		</dependency>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-test</artifactId>
+			<version>${spring.boot.version}</version>
+			<scope>test</scope>
+		</dependency>
 		<dependency>
 			<groupId>org.springframework.security</groupId>
 			<artifactId>spring-security-jwt</artifactId>
diff --git a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServicePropertySourceFactory.java b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServicePropertySourceFactory.java
index 2e50f125..6cb3eb35 100644
--- a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServicePropertySourceFactory.java
+++ b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServicePropertySourceFactory.java
@@ -3,6 +3,7 @@ package com.sap.cloud.security.xsuaa;
 import java.io.IOException;
 import java.util.Properties;
 
+import com.sap.cloud.security.xsuaa.autoconfiguration.XsuaaAutoConfiguration;
 import net.minidev.json.parser.ParseException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -12,6 +13,8 @@ import org.springframework.core.io.support.EncodedResource;
 import org.springframework.core.io.support.PropertySourceFactory;
 
 /**
+ * Part of Auto Configuration {@link XsuaaAutoConfiguration}
+ *
  * <h2>Example Usage</h2>
  * 
  * <pre class="code">
@@ -24,18 +27,16 @@ import org.springframework.core.io.support.PropertySourceFactory;
  * 
  * &#64;Value("${xsuaa.url:}")
  * </pre>
- * 
- *
  *
  */
 public class XsuaaServicePropertySourceFactory implements PropertySourceFactory {
 	private final Logger logger = LoggerFactory.getLogger(getClass());
 	protected static final String XSUAA_PREFIX = "xsuaa.";
 	private static final String XSUAA_PROPERTYIES_KEY = "xsuaa";
-	public final String CLIENT_ID = "xsuaa.clientid";
-	public final String CLIENT_SECRET = "xsuaa.clientsecret";
-	public final String URL = "xsuaa.url";
-	public final String UAA_DOMAIN = "xsuaa.uaadomain";
+	public static final String CLIENT_ID = "xsuaa.clientid";
+	public static final String CLIENT_SECRET = "xsuaa.clientsecret";
+	public static final String URL = "xsuaa.url";
+	public static final String UAA_DOMAIN = "xsuaa.uaadomain";
 
 	private static final String[] XSUAA_ATTRIBUTES = new String[] { "clientid", "clientsecret", "identityzoneid",
 			"sburl", "tenantid", "tenantmode", "uaadomain", "url", "verificationkey", "xsappname" };
diff --git a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/autoconfiguration/XsuaaAutoConfiguration.java b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/autoconfiguration/XsuaaAutoConfiguration.java
new file mode 100644
index 00000000..ec9b1ac4
--- /dev/null
+++ b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/autoconfiguration/XsuaaAutoConfiguration.java
@@ -0,0 +1,42 @@
+package com.sap.cloud.security.xsuaa.autoconfiguration;
+
+import com.sap.cloud.security.xsuaa.XsuaaServiceConfiguration;
+import com.sap.cloud.security.xsuaa.XsuaaServiceConfigurationDefault;
+import com.sap.cloud.security.xsuaa.XsuaaServicePropertySourceFactory;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.PropertySource;
+import org.springframework.security.oauth2.jwt.Jwt;
+
+/**
+ * {@link EnableAutoConfiguration Auto-configuration} for default beans used by
+ * the XSUAA client library.
+ * <p>
+ * Activates when there is a bean of type {@link Jwt} configured in the context.
+ *
+ * <p>
+ * can be disabled
+ * with @EnableAutoConfiguration(exclude={XsuaaAutoConfiguration.class}) or with
+ * property spring.xsuaa.auto = false
+ */
+@Configuration
+@ConditionalOnClass(Jwt.class)
+@ConditionalOnProperty(prefix = "spring.xsuaa", name = "auto", havingValue = "true", matchIfMissing = true)
+public class XsuaaAutoConfiguration {
+
+	@Configuration
+	@PropertySource(factory = XsuaaServicePropertySourceFactory.class, value = { "" })
+	@ConditionalOnProperty(prefix = "spring.xsuaa", name = "multiple-bindings", havingValue = "false", matchIfMissing = true)
+	public static class XsuaaServiceAutoConfiguration {
+
+		@Bean
+		@ConditionalOnMissingBean(XsuaaServiceConfiguration.class)
+		public XsuaaServiceConfiguration xsuaaServiceConfiguration() {
+			return new XsuaaServiceConfigurationDefault();
+		}
+	}
+}
diff --git a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/autoconfiguration/XsuaaResourceServerJwkAutoConfiguration.java b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/autoconfiguration/XsuaaResourceServerJwkAutoConfiguration.java
new file mode 100644
index 00000000..1a1362c7
--- /dev/null
+++ b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/autoconfiguration/XsuaaResourceServerJwkAutoConfiguration.java
@@ -0,0 +1,42 @@
+package com.sap.cloud.security.xsuaa.autoconfiguration;
+
+import com.sap.cloud.security.xsuaa.XsuaaServiceConfiguration;
+import com.sap.cloud.security.xsuaa.token.authentication.XsuaaJwtDecoderBuilder;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+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.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.jwt.JwtDecoder;
+
+/**
+ * {@link EnableAutoConfiguration Auto-configuration} that exposes a
+ * {@link JwtDecoder}, which has the standard Spring Security Jwt validators as
+ * well as the XSUAA-specific validators.
+ *
+ * Activates when there is a bean of type {@link Jwt} configured in the context.
+ *
+ * <p>
+ * can be disabled
+ * with @EnableAutoConfiguration(exclude={XsuaaResourceServerJwkAutoConfiguration.class})
+ * or with property spring.xsuaa.auto = false
+ */
+@Configuration
+@ConditionalOnClass(Jwt.class)
+@ConditionalOnProperty(prefix = "spring.xsuaa", name = "auto", havingValue = "true", matchIfMissing = true)
+@AutoConfigureBefore(OAuth2ResourceServerAutoConfiguration.class) // imports OAuth2ResourceServerJwtConfiguration which
+																	// specifies JwtDecoder
+public class XsuaaResourceServerJwkAutoConfiguration {
+
+	@Bean
+	@ConditionalOnBean(XsuaaServiceConfiguration.class)
+	@ConditionalOnMissingBean
+	public JwtDecoder xsuaaJwtDecoder(XsuaaServiceConfiguration xsuaaServiceConfiguration) {
+		return new XsuaaJwtDecoderBuilder(xsuaaServiceConfiguration).build();
+	}
+}
diff --git a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaJwtDecoder.java b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaJwtDecoder.java
index f255f4f5..aeacfab7 100644
--- a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaJwtDecoder.java
+++ b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaJwtDecoder.java
@@ -1,47 +1,33 @@
 package com.sap.cloud.security.xsuaa.token.authentication;
 
 import java.text.ParseException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
 import java.util.concurrent.TimeUnit;
 
-import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
-import org.springframework.security.oauth2.core.OAuth2TokenValidator;
-import org.springframework.security.oauth2.jwt.Jwt;
-import org.springframework.security.oauth2.jwt.JwtDecoder;
-import org.springframework.security.oauth2.jwt.JwtException;
-import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
-import org.springframework.security.oauth2.jwt.NimbusJwtDecoderJwkSupport;
-
 import com.github.benmanes.caffeine.cache.Cache;
 import com.github.benmanes.caffeine.cache.Caffeine;
 import com.nimbusds.jwt.JWT;
 import com.nimbusds.jwt.JWTParser;
 import com.sap.cloud.security.xsuaa.XsuaaServiceConfiguration;
-
 import net.minidev.json.JSONObject;
+import org.springframework.security.oauth2.core.OAuth2TokenValidator;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.jwt.JwtDecoder;
+import org.springframework.security.oauth2.jwt.JwtException;
+import org.springframework.security.oauth2.jwt.NimbusJwtDecoderJwkSupport;
 import org.springframework.util.Assert;
 
 public class XsuaaJwtDecoder implements JwtDecoder {
 
 	Cache<String, JwtDecoder> cache;
 	private XsuaaServiceConfiguration xsuaaServiceConfiguration;
-	private List<OAuth2TokenValidator<Jwt>> tokenValidators = new ArrayList<>();
+	private OAuth2TokenValidator<Jwt> tokenValidators;
 
 	XsuaaJwtDecoder(XsuaaServiceConfiguration xsuaaServiceConfiguration, int cacheValidityInSeconds, int cacheSize,
-			OAuth2TokenValidator<Jwt>... tokenValidators) {
+			OAuth2TokenValidator<Jwt> tokenValidators) {
 		cache = Caffeine.newBuilder().expireAfterWrite(cacheValidityInSeconds, TimeUnit.SECONDS).maximumSize(cacheSize)
 				.build();
 		this.xsuaaServiceConfiguration = xsuaaServiceConfiguration;
-		// configure token validators
-		this.tokenValidators.add(new JwtTimestampValidator());
-
-		if (tokenValidators == null) {
-			this.tokenValidators.add(new XsuaaAudienceValidator(xsuaaServiceConfiguration));
-		} else {
-			this.tokenValidators.addAll(Arrays.asList(tokenValidators));
-		}
+		this.tokenValidators = tokenValidators;
 	}
 
 	@Override
@@ -62,7 +48,7 @@ public class XsuaaJwtDecoder implements JwtDecoder {
 	protected JwtDecoder getDecoder(String zid, String subdomain) {
 		String url = xsuaaServiceConfiguration.getTokenKeyUrl(zid, subdomain);
 		NimbusJwtDecoderJwkSupport decoder = new NimbusJwtDecoderJwkSupport(url);
-		decoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>(tokenValidators));
+		decoder.setJwtValidator(tokenValidators);
 		return decoder;
 	}
 
diff --git a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaJwtDecoderBuilder.java b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaJwtDecoderBuilder.java
index 9991275b..f4439fab 100644
--- a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaJwtDecoderBuilder.java
+++ b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaJwtDecoderBuilder.java
@@ -1,16 +1,19 @@
 package com.sap.cloud.security.xsuaa.token.authentication;
 
 import com.sap.cloud.security.xsuaa.XsuaaServiceConfiguration;
+import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
 import org.springframework.security.oauth2.core.OAuth2TokenValidator;
 import org.springframework.security.oauth2.jwt.Jwt;
 import org.springframework.security.oauth2.jwt.JwtDecoder;
+import org.springframework.security.oauth2.jwt.JwtValidators;
 
 public class XsuaaJwtDecoderBuilder {
 
 	private XsuaaServiceConfiguration configuration;
-	int decoderCacheValidity = 900; // in seconds
-	int decoderCacheSize = 100;
-	OAuth2TokenValidator<Jwt>[] tokenValidators;
+	int decoderCacheValidity; // in seconds
+	int decoderCacheSize;
+	OAuth2TokenValidator<Jwt> xsuaaTokenValidators;
+	OAuth2TokenValidator<Jwt> defaultTokenValidators;
 
 	/**
 	 * Utility for building a JWT decoder configuration
@@ -20,6 +23,10 @@ public class XsuaaJwtDecoderBuilder {
 	 */
 	public XsuaaJwtDecoderBuilder(XsuaaServiceConfiguration configuration) {
 		this.configuration = configuration;
+		withDefaultValidators(JwtValidators.createDefault());
+		withTokenValidators(new XsuaaAudienceValidator(configuration));
+		withDecoderCacheSize(100);
+		withDecoderCacheTime(900);
 	}
 
 	/**
@@ -28,7 +35,10 @@ public class XsuaaJwtDecoderBuilder {
 	 * @return JwtDecoder
 	 */
 	public JwtDecoder build() {
-		return new XsuaaJwtDecoder(configuration, decoderCacheValidity, decoderCacheSize, tokenValidators);
+		DelegatingOAuth2TokenValidator<Jwt> combinedTokenValidators = new DelegatingOAuth2TokenValidator<>(
+				defaultTokenValidators,
+				xsuaaTokenValidators);
+		return new XsuaaJwtDecoder(configuration, decoderCacheValidity, decoderCacheSize, combinedTokenValidators);
 	}
 
 	/**
@@ -66,8 +76,12 @@ public class XsuaaJwtDecoderBuilder {
 	 * @return this
 	 */
 	public XsuaaJwtDecoderBuilder withTokenValidators(OAuth2TokenValidator<Jwt>... tokenValidators) {
-		this.tokenValidators = tokenValidators;
+		this.xsuaaTokenValidators = new DelegatingOAuth2TokenValidator<>(tokenValidators);
 		return this;
 	}
 
+	public XsuaaJwtDecoderBuilder withDefaultValidators(OAuth2TokenValidator<Jwt>... defaultTokenValidators) {
+		this.defaultTokenValidators = new DelegatingOAuth2TokenValidator<>(defaultTokenValidators);
+		return this;
+	}
 }
diff --git a/spring-xsuaa/src/main/resources/META-INF/spring.factories b/spring-xsuaa/src/main/resources/META-INF/spring.factories
new file mode 100644
index 00000000..b29c44bd
--- /dev/null
+++ b/spring-xsuaa/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,3 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+com.sap.cloud.security.xsuaa.autoconfiguration.XsuaaAutoConfiguration,\
+com.sap.cloud.security.xsuaa.autoconfiguration.XsuaaResourceServerJwkAutoConfiguration
\ No newline at end of file


Test patch:
diff --git a/spring-xsuaa-it/src/main/java/testservice/api/basic/SecurityConfiguration.java b/spring-xsuaa-it/src/main/java/testservice/api/basic/SecurityConfiguration.java
index 326b8dc6..4d810917 100644
--- a/spring-xsuaa-it/src/main/java/testservice/api/basic/SecurityConfiguration.java
+++ b/spring-xsuaa-it/src/main/java/testservice/api/basic/SecurityConfiguration.java
@@ -29,14 +29,12 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
 import org.springframework.security.oauth2.jwt.Jwt;
-import org.springframework.security.oauth2.jwt.JwtDecoder;
 
 import com.sap.cloud.security.xsuaa.XsuaaServiceConfiguration;
 import com.sap.cloud.security.xsuaa.extractor.AuthenticationMethod;
 import com.sap.cloud.security.xsuaa.extractor.TokenBrokerResolver;
 import com.sap.cloud.security.xsuaa.mock.MockXsuaaServiceConfiguration;
 import com.sap.cloud.security.xsuaa.token.TokenAuthenticationConverter;
-import com.sap.cloud.security.xsuaa.token.authentication.XsuaaJwtDecoderBuilder;
 
 @EnableWebSecurity
 @EnableCaching
@@ -72,11 +70,6 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
 		return new MySecurityConfiguration();
 	}
 
-	@Bean
-	JwtDecoder jwtDecoder() {
-		return new XsuaaJwtDecoderBuilder(getXsuaaServiceConfiguration()).build();
-	}
-
 	private class MySecurityConfiguration extends MockXsuaaServiceConfiguration {
 
 		@Override
diff --git a/spring-xsuaa-it/src/main/java/testservice/api/nohttp/SecurityConfiguration.java b/spring-xsuaa-it/src/main/java/testservice/api/nohttp/SecurityConfiguration.java
index 83987cac..abbd5c55 100644
--- a/spring-xsuaa-it/src/main/java/testservice/api/nohttp/SecurityConfiguration.java
+++ b/spring-xsuaa-it/src/main/java/testservice/api/nohttp/SecurityConfiguration.java
@@ -18,11 +18,9 @@ package testservice.api.nohttp;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Profile;
-import org.springframework.security.oauth2.jwt.JwtDecoder;
 
 import com.sap.cloud.security.xsuaa.XsuaaServiceConfiguration;
 import com.sap.cloud.security.xsuaa.mock.MockXsuaaServiceConfiguration;
-import com.sap.cloud.security.xsuaa.token.authentication.XsuaaJwtDecoderBuilder;
 
 @Configuration
 // @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
@@ -33,10 +31,4 @@ public class SecurityConfiguration {
 	XsuaaServiceConfiguration getXsuaaServiceConfiguration() {
 		return new MockXsuaaServiceConfiguration();
 	}
-
-	@Bean
-	JwtDecoder jwtDecoder() {
-		return new XsuaaJwtDecoderBuilder(getXsuaaServiceConfiguration()).build();
-	}
-
 }
diff --git a/spring-xsuaa-it/src/main/java/testservice/api/v1/SecurityConfiguration.java b/spring-xsuaa-it/src/main/java/testservice/api/v1/SecurityConfiguration.java
index b760e1da..c50716c7 100644
--- a/spring-xsuaa-it/src/main/java/testservice/api/v1/SecurityConfiguration.java
+++ b/spring-xsuaa-it/src/main/java/testservice/api/v1/SecurityConfiguration.java
@@ -23,12 +23,10 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
 import org.springframework.security.oauth2.jwt.Jwt;
-import org.springframework.security.oauth2.jwt.JwtDecoder;
 
 import com.sap.cloud.security.xsuaa.XsuaaServiceConfiguration;
 import com.sap.cloud.security.xsuaa.mock.MockXsuaaServiceConfiguration;
 import com.sap.cloud.security.xsuaa.token.TokenAuthenticationConverter;
-import com.sap.cloud.security.xsuaa.token.authentication.XsuaaJwtDecoderBuilder;
 
 @EnableWebSecurity
 @Profile({ "test.api.v1" })
@@ -57,9 +55,4 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
 		return new MockXsuaaServiceConfiguration();
 	}
 
-	@Bean
-	JwtDecoder jwtDecoder() {
-		return new XsuaaJwtDecoderBuilder(getXsuaaServiceConfiguration()).build();
-	}
-
 }
diff --git a/spring-xsuaa-it/src/test/java/testservice/api/InitializeSecurityContextTest.java b/spring-xsuaa-it/src/test/java/testservice/api/InitializeSecurityContextTest.java
index 40edc333..ffef9707 100644
--- a/spring-xsuaa-it/src/test/java/testservice/api/InitializeSecurityContextTest.java
+++ b/spring-xsuaa-it/src/test/java/testservice/api/InitializeSecurityContextTest.java
@@ -13,6 +13,8 @@ import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
 
+import com.sap.cloud.security.xsuaa.autoconfiguration.XsuaaAutoConfiguration;
+import com.sap.cloud.security.xsuaa.autoconfiguration.XsuaaResourceServerJwkAutoConfiguration;
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -37,7 +39,8 @@ import testservice.api.nohttp.MyEventHandler;
 import testservice.api.nohttp.SecurityConfiguration;
 
 @RunWith(SpringRunner.class)
-@SpringBootTest(classes = { SecurityConfiguration.class, MyEventHandler.class })
+@SpringBootTest(classes = { SecurityConfiguration.class, MyEventHandler.class, XsuaaAutoConfiguration.class,
+		XsuaaResourceServerJwkAutoConfiguration.class })
 @ActiveProfiles({ "test.api.nohttp", "uaamock" })
 public class InitializeSecurityContextTest {
 	@Value("${xsuaa.clientid}")
diff --git a/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/autoconfiguration/XsuaaAutoConfigurationTest.java b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/autoconfiguration/XsuaaAutoConfigurationTest.java
new file mode 100644
index 00000000..37cce87d
--- /dev/null
+++ b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/autoconfiguration/XsuaaAutoConfigurationTest.java
@@ -0,0 +1,132 @@
+package com.sap.cloud.security.xsuaa.autoconfiguration;
+
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertThat;
+
+import com.sap.cloud.security.xsuaa.XsuaaServiceConfiguration;
+import com.sap.cloud.security.xsuaa.XsuaaServiceConfigurationDefault;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.AutoConfigurations;
+import org.springframework.boot.test.context.FilteredClassLoader;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = XsuaaAutoConfiguration.class)
+public class XsuaaAutoConfigurationTest {
+
+	// create an ApplicationContextRunner that will create a context with the
+	// configuration under test.
+	private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
+			.withConfiguration(AutoConfigurations.of(XsuaaAutoConfiguration.class));
+
+	@Autowired
+	private ApplicationContext context;
+
+	@Test
+	public final void autoConfigurationActive() {
+		contextRunner.run((context) -> {
+			assertThat(context.containsBean("xsuaaServiceConfiguration"), is(true));
+			assertThat(context.getBean("xsuaaServiceConfiguration"),
+					instanceOf(XsuaaServiceConfigurationDefault.class));
+			assertThat(context.getBean(XsuaaServiceConfiguration.class), is(not(nullValue())));
+		});
+	}
+
+	@Test
+	public final void autoConfigurationActiveInclProperties() {
+		contextRunner
+				.withPropertyValues("spring.xsuaa.auto:true")
+				.withPropertyValues("spring.xsuaa.multiple-bindings:false").run((context) -> {
+					assertThat(context.containsBean("xsuaaServiceConfiguration"), is(true));
+					assertThat(context.getBean("xsuaaServiceConfiguration"),
+							instanceOf(XsuaaServiceConfigurationDefault.class));
+					assertThat(context.getBean(XsuaaServiceConfiguration.class), is(not(nullValue())));
+				});
+	}
+
+	@Test
+	public void autoConfigurationDisabledByProperty() {
+		contextRunner.withPropertyValues("spring.xsuaa.auto:false").run((context) -> {
+			assertThat(context.containsBean("xsuaaServiceConfiguration"), is(false));
+		});
+	}
+
+	@Test
+	public void serviceConfigurationDisabledByProperty() {
+		contextRunner.withPropertyValues("spring.xsuaa.multiple-bindings:true").run((context) -> {
+			assertThat(context.containsBean("xsuaaServiceConfiguration"), is(false));
+		});
+	}
+
+	@Test
+	public final void autoConfigurationWithoutJwtOnClasspathInactive() {
+		contextRunner.withClassLoader(new FilteredClassLoader(Jwt.class)) // removes Jwt.class from classpath
+				.run((context) -> {
+					assertThat(context.containsBean("xsuaaServiceConfiguration"), is(false));
+				});
+	}
+
+	@Test
+	public final void userConfigurationCanOverrideDefaultBeans() {
+		contextRunner.withUserConfiguration(UserConfiguration.class)
+				.run((context) -> {
+					assertThat(context.containsBean("xsuaaServiceConfiguration"), is(false));
+					assertThat(context.containsBean("customServiceConfiguration"), is(true));
+					assertThat(context.getBean("customServiceConfiguration"),
+							instanceOf(CustomXsuaaConfiguration.class));
+				});
+	}
+
+	@Configuration
+	public static class UserConfiguration {
+
+		@Bean
+		public XsuaaServiceConfiguration customServiceConfiguration() {
+			return new CustomXsuaaConfiguration();
+		}
+	}
+
+	static class CustomXsuaaConfiguration implements XsuaaServiceConfiguration {
+
+		@Override
+		public String getClientId() {
+			return null;
+		}
+
+		@Override
+		public String getClientSecret() {
+			return null;
+		}
+
+		@Override
+		public String getUaaUrl() {
+			return null;
+		}
+
+		@Override
+		public String getTokenKeyUrl(String zid, String subdomain) {
+			return null;
+		}
+
+		@Override
+		public String getAppId() {
+			return null;
+		}
+
+		@Override
+		public String getUaaDomain() {
+			return null;
+		}
+	}
+}
diff --git a/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/autoconfiguration/XsuaaResourceServerJwkAutoConfigurationTest.java b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/autoconfiguration/XsuaaResourceServerJwkAutoConfigurationTest.java
new file mode 100644
index 00000000..8c6e85b4
--- /dev/null
+++ b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/autoconfiguration/XsuaaResourceServerJwkAutoConfigurationTest.java
@@ -0,0 +1,93 @@
+package com.sap.cloud.security.xsuaa.autoconfiguration;
+
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertThat;
+
+import com.sap.cloud.security.xsuaa.token.authentication.XsuaaJwtDecoder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.AutoConfigurations;
+import org.springframework.boot.test.context.FilteredClassLoader;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.jwt.JwtDecoder;
+import org.springframework.security.oauth2.jwt.NimbusJwtDecoderJwkSupport;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = { XsuaaResourceServerJwkAutoConfiguration.class, XsuaaAutoConfiguration.class })
+public class XsuaaResourceServerJwkAutoConfigurationTest {
+
+	// create an ApplicationContextRunner that will create a context with the
+	// configuration under test.
+	private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
+			.withConfiguration(
+					AutoConfigurations.of(XsuaaResourceServerJwkAutoConfiguration.class, XsuaaAutoConfiguration.class));
+
+	@Autowired
+	private ApplicationContext context;
+
+	@Test
+	public final void autoConfigurationActive() {
+		contextRunner.run((context) -> {
+			assertThat(context.containsBean("xsuaaJwtDecoder"), is(true));
+			assertThat(context.getBean("xsuaaJwtDecoder"), instanceOf(XsuaaJwtDecoder.class));
+			assertThat(context.getBean(JwtDecoder.class), is(not(nullValue())));
+			assertThat(context.getBean(JwtDecoder.class), instanceOf(XsuaaJwtDecoder.class));
+		});
+	}
+
+	@Test
+	public final void autoConfigurationActiveInclProperties() {
+		contextRunner
+				.withPropertyValues("spring.xsuaa.auto:true").run((context) -> {
+					assertThat(context.containsBean("xsuaaJwtDecoder"), is(true));
+					assertThat(context.getBean("xsuaaJwtDecoder"), instanceOf(XsuaaJwtDecoder.class));
+					assertThat(context.getBean(JwtDecoder.class), is(not(nullValue())));
+				});
+	}
+
+	@Test
+	public void autoConfigurationDisabledByProperty() {
+		contextRunner.withPropertyValues("spring.xsuaa.auto:false").run((context) -> {
+			assertThat(context.containsBean("xsuaaJwtDecoder"), is(false));
+		});
+	}
+
+	@Test
+	public final void autoConfigurationWithoutXsuaaServiceConfigurationOnClasspathInactive() {
+		contextRunner.withClassLoader(
+				new FilteredClassLoader(Jwt.class)) // make sure XsuaaServiceConfiguration.class is not on the classpath
+				.run((context) -> {
+					assertThat(context.containsBean("xsuaaJwtDecoder"), is(false));
+				});
+	}
+
+	@Test
+	public final void userConfigurationCanOverrideDefaultBeans() {
+		contextRunner.withUserConfiguration(UserConfiguration.class)
+				.run((context) -> {
+					assertThat(context.containsBean("xsuaaJwtDecoder"), is(false));
+					assertThat(context.containsBean("customJwtDecoder"), is(true));
+					assertThat(context.getBean("customJwtDecoder"),
+							instanceOf(NimbusJwtDecoderJwkSupport.class));
+				});
+	}
+
+	@Configuration
+	public static class UserConfiguration {
+
+		@Bean
+		public JwtDecoder customJwtDecoder() {
+			return new NimbusJwtDecoderJwkSupport("http://localhost:8080/uaa/oauth/token_keys");
+		}
+	}
+}