Repository: SAP/cloud-security-xsuaa-integration
Pull request #204 (2020-01-08 10:55:42)

Corrections on 2.4.0-SNAPSHOT
- improve / add javadoc
- simplify TestSecurityRule
- simplified some Apis
- correct fallback handling with verifcationkey as public key for legacy projects
- apply filter for Jetty test server so that requests gets not routed to the servlet
- builder for OAuth2ServiceConfiguration
- removed CFOAuth2ServiceConfiguration



Patch:
diff --git a/api/src/main/java/com/sap/xsa/security/container/XSPrincipal.java b/api/src/main/java/com/sap/xsa/security/container/XSPrincipal.java
index d7ec8af8..072dbf33 100644
--- a/api/src/main/java/com/sap/xsa/security/container/XSPrincipal.java
+++ b/api/src/main/java/com/sap/xsa/security/container/XSPrincipal.java
@@ -8,6 +8,10 @@ package com.sap.xsa.security.container;
 
 import java.security.Principal;
 
+/**
+ * @Deprecated with version 2.4.0 in favor of the new SAP Java Container library.
+ */
+@Deprecated
 public interface XSPrincipal extends Principal {
 
 }
diff --git a/api/src/main/java/com/sap/xsa/security/container/XSTokenRequest.java b/api/src/main/java/com/sap/xsa/security/container/XSTokenRequest.java
index f21ecef5..652d132e 100644
--- a/api/src/main/java/com/sap/xsa/security/container/XSTokenRequest.java
+++ b/api/src/main/java/com/sap/xsa/security/container/XSTokenRequest.java
@@ -11,7 +11,9 @@ import java.util.Map;
 
 /**
  * Represents a token exchange request.
+ * @Deprecated with version 2.4.0 in favor of the new SAP Java Container library.
  */
+@Deprecated
 public interface XSTokenRequest {
 
 	public static final int TYPE_USER_TOKEN = 0;
diff --git a/api/src/main/java/com/sap/xsa/security/container/XSUserInfo.java b/api/src/main/java/com/sap/xsa/security/container/XSUserInfo.java
index 18ed889e..8cdce9d7 100644
--- a/api/src/main/java/com/sap/xsa/security/container/XSUserInfo.java
+++ b/api/src/main/java/com/sap/xsa/security/container/XSUserInfo.java
@@ -9,7 +9,10 @@ package com.sap.xsa.security.container;
 /**
  * API for OAuth resource servers to extract authentication and authorization
  * information from the OAuth token.
+ *
+ * @Deprecated with version 2.4.0 in favor of the new SAP Java Container library.
  */
+@Deprecated
 public interface XSUserInfo {
 
 	/**
diff --git a/api/src/main/java/com/sap/xsa/security/container/XSUserInfoException.java b/api/src/main/java/com/sap/xsa/security/container/XSUserInfoException.java
index 1f9ff264..337997ee 100644
--- a/api/src/main/java/com/sap/xsa/security/container/XSUserInfoException.java
+++ b/api/src/main/java/com/sap/xsa/security/container/XSUserInfoException.java
@@ -1,12 +1,16 @@
 /**
  * Copyright (c) 2018 SAP SE or an SAP affiliate company. All rights reserved.
- * This file is licensed under the Apache Software License, 
- * v. 2 except as noted otherwise in the LICENSE file 
+ * This file is licensed under the Apache Software License,
+ * v. 2 except as noted otherwise in the LICENSE file
  * https://github.com/SAP/cloud-security-xsuaa-integration/blob/master/LICENSE
  */
 package com.sap.xsa.security.container;
 
-public class XSUserInfoException extends Exception {
+/**
+ * @Deprecated with version 2.4.0 in favor of the new SAP Java Container library.
+ */
+@Deprecated
+ public class XSUserInfoException extends Exception {
 
 	private static final long serialVersionUID = 1L;
 
diff --git a/java-security/README.md b/java-security/README.md
index 8cc5b8d0..90661ec6 100644
--- a/java-security/README.md
+++ b/java-security/README.md
@@ -14,7 +14,7 @@ A Java implementation of JSON Web Token (JWT) - [RFC 7519](https://tools.ietf.or
   - Is the JWT signed with the public key of the trust-worthy identity service? With that it also makes sure that the payload and the header of the JWT is unchanged ([`JwtSignatureValidator`](
  src/main/java/com/sap/cloud/security/token/validation/validators/JwtSignatureValidator.java))?
 - Provides thread-local cache ([`SecurityContext`](src/main/java/com/sap/cloud/security/token/SecurityContext.java)) to store the decoded and validated token.
-- Provides an authenticator ([`TokenAuthenticator`](src/main/java/com/sap/cloud/security/servlet/TokenAuthenticator.java)) that validates bearer tokens contained in the authorization header of HTTP requests. The authenticator is used in the following [sample](/samples/java-security-usage).
+- Furthermore, it provides an authenticator ([`TokenAuthenticator`](src/main/java/com/sap/cloud/security/servlet/TokenAuthenticator.java)) that validates bearer tokens contained in the authorization header of HTTP requests. The authenticator is used in the following [sample](/samples/java-security-usage).
 ## Open Source libs used
 - JSON Parser Reference implementation: [json.org](https://github.com/stleary/JSON-java)
 - No crypto library. Leverages Public Key Infrastructure (PKI) provided by Java Security Framework to verify digital signatures.
diff --git a/java-security/src/main/java/com/sap/cloud/security/config/Environment.java b/java-security/src/main/java/com/sap/cloud/security/config/Environment.java
index 091dc6dd..6bb45631 100644
--- a/java-security/src/main/java/com/sap/cloud/security/config/Environment.java
+++ b/java-security/src/main/java/com/sap/cloud/security/config/Environment.java
@@ -4,11 +4,14 @@ import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
 /**
- * Central entry point to access the OAuth configuration of a supported identity
- * {@link Service}.
+ * Central entry point to access the OAuth configuration
+ * ({@link OAuth2ServiceConfiguration}) of a supported identity {@link Service}.
  */
 public interface Environment {
 
+	/**
+	 * Represents a supported SAP CP environment.
+	 */
 	enum Type {
 		CF /* , KUBERNETES */;
 		public static Type from(String typeAsString) {
diff --git a/java-security/src/main/java/com/sap/cloud/security/config/Environments.java b/java-security/src/main/java/com/sap/cloud/security/config/Environments.java
index d3cb8739..d7b343eb 100644
--- a/java-security/src/main/java/com/sap/cloud/security/config/Environments.java
+++ b/java-security/src/main/java/com/sap/cloud/security/config/Environments.java
@@ -2,6 +2,10 @@ package com.sap.cloud.security.config;
 
 import com.sap.cloud.security.config.cf.CFEnvironment;
 
+/**
+ * Central entry point to access the current SAP Cloud Platform
+ * {@link Environment}.
+ */
 public class Environments {
 
 	private static final Environment cfEnvironment = CFEnvironment.getInstance(); // singleton
@@ -10,6 +14,9 @@ public class Environments {
 		// use factoryMethods instead
 	}
 
+	/**
+	 * Determines the current type of {@link Environment}.
+	 */
 	public static Environment getCurrent() {
 		// TODO Kubernetes: probe in which environemt it runs currently: CF or
 		// Kubernetes, e.g.
diff --git a/java-security/src/main/java/com/sap/cloud/security/config/OAuth2ServiceConfiguration.java b/java-security/src/main/java/com/sap/cloud/security/config/OAuth2ServiceConfiguration.java
index 7f1b89c0..8b41199b 100644
--- a/java-security/src/main/java/com/sap/cloud/security/config/OAuth2ServiceConfiguration.java
+++ b/java-security/src/main/java/com/sap/cloud/security/config/OAuth2ServiceConfiguration.java
@@ -4,24 +4,27 @@ import javax.annotation.Nullable;
 
 import java.net.URI;
 
+/**
+ * Provides information of the identity {@link Service}.
+ */
 public interface OAuth2ServiceConfiguration {
 
 	/**
-	 * Client id of xsuaa service instance
+	 * Client id of identity service instance.
 	 *
-	 * @return clientId
+	 * @return client identifier
 	 */
 	String getClientId();
 
 	/**
-	 * Client secret of xsuaa instance
+	 * Client secret of identity service instance.
 	 *
 	 * @return client secret
 	 */
 	String getClientSecret();
 
 	/**
-	 * Base URL of the OAuth2 Identity service instance. In multi tenancy scenarios
+	 * Base URL of the OAuth2 identity service instance. In multi tenancy scenarios
 	 * this is the url where the service instance was created.
 	 *
 	 * @return base url, e.g. https://paastenant.idservice.com
@@ -51,7 +54,7 @@ public interface OAuth2ServiceConfiguration {
 	boolean hasProperty(String name);
 
 	/**
-	 * Returns the service of this configuration.
+	 * Returns the identity {@link Service} of this configuration.
 	 *
 	 * @return the service.
 	 */
diff --git a/java-security/src/main/java/com/sap/cloud/security/config/OAuth2ServiceConfigurationBuilder.java b/java-security/src/main/java/com/sap/cloud/security/config/OAuth2ServiceConfigurationBuilder.java
new file mode 100644
index 00000000..5ca01552
--- /dev/null
+++ b/java-security/src/main/java/com/sap/cloud/security/config/OAuth2ServiceConfigurationBuilder.java
@@ -0,0 +1,121 @@
+package com.sap.cloud.security.config;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.sap.cloud.security.config.cf.CFConstants.*;
+
+/**
+ * Builds an OAuth configuration ({@link OAuth2ServiceConfiguration}) for a
+ * dedicated identity ({@link Service}) based on the properties applied.
+ */
+public class OAuth2ServiceConfigurationBuilder {
+	private Service service;
+	private final Map<String, String> properties = new HashMap<>();
+
+	private OAuth2ServiceConfigurationBuilder() {
+		// use forService factory method
+	}
+
+	/**
+	 * Creates a builder for a dedicated identity ({@link Service})
+	 * 
+	 * @param service
+	 *            the service
+	 * @return this builder
+	 */
+	public static OAuth2ServiceConfigurationBuilder forService(Service service) {
+		OAuth2ServiceConfigurationBuilder instance = new OAuth2ServiceConfigurationBuilder();
+		instance.service = service;
+		return instance;
+	}
+
+	/**
+	 * Client id of identity service instance.
+	 *
+	 * @param clientId
+	 *            client identifier
+	 * @return this builder itself
+	 */
+	public OAuth2ServiceConfigurationBuilder withClientId(String clientId) {
+		properties.put(CLIENT_ID, clientId);
+		return this;
+	}
+
+	/**
+	 * Client secret of identity service instance.
+	 *
+	 * @param clientSecret
+	 *            client secret
+	 * @return this builder itself
+	 */
+	public OAuth2ServiceConfigurationBuilder withClientSecret(String clientSecret) {
+		properties.put(CLIENT_SECRET, clientSecret);
+		return this;
+	}
+
+	/**
+	 * Base URL of the OAuth2 identity service instance. In multi tenancy scenarios
+	 * this is the url where the service instance was created.
+	 *
+	 * @return base url, e.g. https://paastenant.idservice.com
+	 */
+	public OAuth2ServiceConfigurationBuilder withUrl(String url) {
+		properties.put(URL, url);
+		return this;
+	}
+
+	public OAuth2ServiceConfigurationBuilder withProperty(String propertyName, String propertyValue) {
+		properties.put(propertyName, propertyValue);
+		return this;
+	}
+
+	public OAuth2ServiceConfigurationBuilder withProperties(Map<String, String> properties) {
+		properties.forEach((key, value) -> withProperty(key, value));
+		return this;
+	}
+
+	/**
+	 * Builds an OAuth configuration ({@link OAuth2ServiceConfiguration}) based on
+	 * the properties applied.
+	 * 
+	 * @return the oauth2 service configuration.
+	 */
+	public OAuth2ServiceConfiguration build() {
+		return new OAuth2ServiceConfiguration() {
+
+			@Override
+			public String getClientId() {
+				return properties.get(CLIENT_ID);
+			}
+
+			@Override
+			public String getClientSecret() {
+				return properties.get(CLIENT_SECRET);
+			}
+
+			@Override
+			public URI getUrl() {
+				return URI.create(properties.get(URL));
+			}
+
+			@Override
+			public String getProperty(String name) {
+				return properties.get(name);
+			}
+
+			@Override
+			public boolean hasProperty(String name) {
+				return properties.containsKey(name);
+			}
+
+			@Override
+			public Service getService() {
+				return service;
+			}
+		};
+
+	}
+
+}
diff --git a/java-security/src/main/java/com/sap/cloud/security/config/Service.java b/java-security/src/main/java/com/sap/cloud/security/config/Service.java
index 93916f32..bfbce9e5 100644
--- a/java-security/src/main/java/com/sap/cloud/security/config/Service.java
+++ b/java-security/src/main/java/com/sap/cloud/security/config/Service.java
@@ -1,5 +1,8 @@
 package com.sap.cloud.security.config;
 
+/**
+ * Represents a supported identity service.
+ */
 public enum Service {
 	XSUAA("xsuaa"), IAS("iasb");
 
diff --git a/java-security/src/main/java/com/sap/cloud/security/config/cf/CFConstants.java b/java-security/src/main/java/com/sap/cloud/security/config/cf/CFConstants.java
index f6b8dc0e..2d81b728 100644
--- a/java-security/src/main/java/com/sap/cloud/security/config/cf/CFConstants.java
+++ b/java-security/src/main/java/com/sap/cloud/security/config/cf/CFConstants.java
@@ -1,5 +1,9 @@
 package com.sap.cloud.security.config.cf;
 
+/**
+ * Constants that simplifies access to service configuration properties in the
+ * Cloud Foundry environment.
+ */
 public class CFConstants {
 	public static final String VCAP_SERVICES = "VCAP_SERVICES";
 	public static final String CREDENTIALS = "credentials";
@@ -11,6 +15,9 @@ public class CFConstants {
 	private CFConstants() {
 	}
 
+	/**
+	 * Constants that are specific to the Xsuaa identity service.
+	 */
 	public static class XSUAA {
 		private XSUAA() {
 		}
@@ -19,6 +26,9 @@ public class CFConstants {
 		public static final String APP_ID = "xsappname";
 	}
 
+	/**
+	 * Constants that are specific to the Ias identity service.
+	 */
 	public static class IAS {
 		private IAS() {
 		}
@@ -35,5 +45,6 @@ public class CFConstants {
 		public static Plan from(String planAsString) {
 			return Plan.valueOf(planAsString.toUpperCase());
 		}
+
 	}
 }
diff --git a/java-security/src/main/java/com/sap/cloud/security/config/cf/CFEnvParser.java b/java-security/src/main/java/com/sap/cloud/security/config/cf/CFEnvParser.java
index 9c9f448b..abd27411 100644
--- a/java-security/src/main/java/com/sap/cloud/security/config/cf/CFEnvParser.java
+++ b/java-security/src/main/java/com/sap/cloud/security/config/cf/CFEnvParser.java
@@ -1,5 +1,7 @@
 package com.sap.cloud.security.config.cf;
 
+import com.sap.cloud.security.config.OAuth2ServiceConfiguration;
+import com.sap.cloud.security.config.OAuth2ServiceConfigurationBuilder;
 import com.sap.cloud.security.config.Service;
 import com.sap.cloud.security.json.DefaultJsonObject;
 import com.sap.cloud.security.json.JsonObject;
@@ -10,6 +12,7 @@ import java.util.*;
 import java.util.stream.Collectors;
 
 import static com.sap.cloud.security.config.Service.XSUAA;
+import static com.sap.cloud.security.config.cf.CFConstants.SERVICE_PLAN;
 
 class CFEnvParser {
 	private static final Logger logger = LoggerFactory.getLogger(CFEnvParser.class);
@@ -23,9 +26,9 @@ class CFEnvParser {
 	 * @return the list of all found configurations or empty list, in case there are
 	 *         not service bindings.
 	 */
-	static Map<Service, List<CFOAuth2ServiceConfiguration>> loadAll(String vcapJsonString) {
-		Map<Service, List<CFOAuth2ServiceConfiguration>> serviceConfigurations = new HashMap<>(); // NOSONAR
-		List<CFOAuth2ServiceConfiguration> allServices;
+	static Map<Service, List<OAuth2ServiceConfiguration>> loadAll(String vcapJsonString) {
+		Map<Service, List<OAuth2ServiceConfiguration>> serviceConfigurations = new HashMap<>(); // NOSONAR
+		List<OAuth2ServiceConfiguration> allServices;
 		for (Service s : Service.values()) {
 			allServices = extractAllServices(s,
 					new DefaultJsonObject(vcapJsonString));
@@ -34,7 +37,7 @@ class CFEnvParser {
 		return serviceConfigurations;
 	}
 
-	static List<CFOAuth2ServiceConfiguration> extractAllServices(Service service, JsonObject vcapJsonObject) {
+	static List<OAuth2ServiceConfiguration> extractAllServices(Service service, JsonObject vcapJsonObject) {
 		List<JsonObject> serviceJsonObjects = vcapJsonObject.getJsonObjects(service.getCFName());
 		if (service == XSUAA && serviceJsonObjects.size() > 1) {
 			logger.warn(
@@ -46,10 +49,15 @@ class CFEnvParser {
 				.collect(Collectors.toList());
 	}
 
-	public static CFOAuth2ServiceConfiguration extract(Service service, JsonObject serviceJsonObject) {
-		Map<String, String> xsuaaConfigMap = serviceJsonObject.getKeyValueMap();
-		Map<String, String> credentialsMap = serviceJsonObject.getJsonObject(CFConstants.CREDENTIALS).getKeyValueMap();
+	public static OAuth2ServiceConfiguration extract(Service service, JsonObject serviceJsonObject) {
+		Map<String, String> serviceBindingProperties = serviceJsonObject.getKeyValueMap();
+		Map<String, String> serviceBindingCredentials = serviceJsonObject.getJsonObject(CFConstants.CREDENTIALS)
+				.getKeyValueMap();
 
-		return new CFOAuth2ServiceConfiguration(service, xsuaaConfigMap, credentialsMap);
+		return OAuth2ServiceConfigurationBuilder.forService(service)
+				.withProperties(serviceBindingCredentials)
+				.withProperty(SERVICE_PLAN, serviceBindingProperties.get(SERVICE_PLAN))
+				.build();
 	}
+
 }
diff --git a/java-security/src/main/java/com/sap/cloud/security/config/cf/CFEnvironment.java b/java-security/src/main/java/com/sap/cloud/security/config/cf/CFEnvironment.java
index ff73363e..a16eb662 100644
--- a/java-security/src/main/java/com/sap/cloud/security/config/cf/CFEnvironment.java
+++ b/java-security/src/main/java/com/sap/cloud/security/config/cf/CFEnvironment.java
@@ -1,6 +1,5 @@
 package com.sap.cloud.security.config.cf;
 
-import static com.sap.cloud.security.config.Service.IAS;
 import static com.sap.cloud.security.config.Service.XSUAA;
 import static com.sap.cloud.security.config.cf.CFConstants.VCAP_SERVICES;
 
@@ -12,13 +11,18 @@ import com.sap.cloud.security.config.Service;
 import com.sap.cloud.security.config.cf.CFConstants.Plan;
 
 import java.util.*;
-import java.util.function.Function;
+import java.util.function.UnaryOperator;
 
+/**
+ * Loads the OAuth configuration ({@link OAuth2ServiceConfiguration}) of a
+ * supported identity {@link Service} in the SAP CP Cloud Foundry Environment by
+ * parsing the {@code VCAP_SERVICES} system environment variable.
+ */
 public class CFEnvironment implements Environment {
 
-	private Map<Service, List<CFOAuth2ServiceConfiguration>> serviceConfigurations;
-	private Function<String, String> systemEnvironmentProvider;
-	private Function<String, String> systemPropertiesProvider;
+	private Map<Service, List<OAuth2ServiceConfiguration>> serviceConfigurations;
+	private UnaryOperator<String> systemEnvironmentProvider;
+	private UnaryOperator<String> systemPropertiesProvider;
 
 	private CFEnvironment() {
 		// implemented in getInstance() factory method
@@ -28,8 +32,8 @@ public class CFEnvironment implements Environment {
 		return getInstance(System::getenv, System::getProperty);
 	}
 
-	static CFEnvironment getInstance(Function<String, String> systemEnvironmentProvider,
-			Function<String, String> systemPropertiesProvider) {
+	static CFEnvironment getInstance(UnaryOperator<String> systemEnvironmentProvider,
+			UnaryOperator<String> systemPropertiesProvider) {
 		CFEnvironment instance = new CFEnvironment();
 		instance.systemEnvironmentProvider = systemEnvironmentProvider;
 		instance.systemPropertiesProvider = systemPropertiesProvider;
@@ -53,13 +57,13 @@ public class CFEnvironment implements Environment {
 
 	@Override
 	public int getNumberOfXsuaaConfigurations() {
-		return loadAll(XSUAA).size();
+		return loadAllForService(XSUAA).size();
 	}
 
 	@Override
 	public OAuth2ServiceConfiguration getXsuaaConfigurationForTokenExchange() {
 		if (getNumberOfXsuaaConfigurations() > 1) {
-			return loadByPlan(XSUAA, Plan.BROKER);
+			return loadForServicePlan(XSUAA, Plan.BROKER);
 		}
 		return getXsuaaConfiguration();
 	}
@@ -70,13 +74,13 @@ public class CFEnvironment implements Environment {
 	 * @param service
 	 *            the name of the service
 	 * @return the list of all found configurations or empty list, in case there are
-	 *         not service bindings.
+	 *         no service bindings.
 	 * @deprecated as multiple bindings of XSUAA identity service is not anymore
 	 *             necessary with the unified broker plan, this method is
 	 *             deprecated.
 	 */
 	@Deprecated
-	List<CFOAuth2ServiceConfiguration> loadAll(Service service) {
+	List<OAuth2ServiceConfiguration> loadAllForService(Service service) {
 		return serviceConfigurations.getOrDefault(service, new ArrayList<>());
 	}
 
@@ -93,21 +97,27 @@ public class CFEnvironment implements Environment {
 		return env != null ? env : "{}";
 	}
 
-	private CFOAuth2ServiceConfiguration loadXsuaa() {
-		Optional<CFOAuth2ServiceConfiguration> applicationService = Optional
-				.ofNullable(loadByPlan(XSUAA, Plan.APPLICATION));
-		Optional<CFOAuth2ServiceConfiguration> brokerService = Optional
-				.ofNullable(loadByPlan(XSUAA, Plan.BROKER));
+	private OAuth2ServiceConfiguration loadXsuaa() {
+		Optional<OAuth2ServiceConfiguration> applicationService = Optional
+				.ofNullable(loadForServicePlan(XSUAA, Plan.APPLICATION));
+		Optional<OAuth2ServiceConfiguration> brokerService = Optional
+				.ofNullable(loadForServicePlan(XSUAA, Plan.BROKER));
 		if (applicationService.isPresent()) {
 			return applicationService.get();
 		}
 		return brokerService.orElse(null);
 	}
 
+	/**
+	 * Loads the configuration for a dedicated service plan.
+	 *
+	 * @return the configuration or null, if there is not such binding information
+	 *         for the given service plan.
+	 */
 	@Nullable
-	public CFOAuth2ServiceConfiguration loadByPlan(Service service, Plan plan) {
-		return loadAll(service).stream()
-				.filter(configuration -> configuration.getPlan() == plan)
+	public OAuth2ServiceConfiguration loadForServicePlan(Service service, Plan plan) {
+		return loadAllForService(service).stream()
+				.filter(configuration -> Plan.from(configuration.getProperty(CFConstants.SERVICE_PLAN)).equals(plan))
 				.findFirst()
 				.orElse(null);
 	}
diff --git a/java-security/src/main/java/com/sap/cloud/security/config/cf/CFOAuth2ServiceConfiguration.java b/java-security/src/main/java/com/sap/cloud/security/config/cf/CFOAuth2ServiceConfiguration.java
deleted file mode 100644
index 3f1b5c9a..00000000
--- a/java-security/src/main/java/com/sap/cloud/security/config/cf/CFOAuth2ServiceConfiguration.java
+++ /dev/null
@@ -1,73 +0,0 @@
-package com.sap.cloud.security.config.cf;
-
-import com.sap.cloud.security.config.OAuth2ServiceConfiguration;
-import com.sap.cloud.security.config.Service;
-
-import javax.annotation.Nullable;
-import java.net.URI;
-import java.util.Map;
-
-import static com.sap.cloud.security.config.cf.CFConstants.*;
-import static com.sap.cloud.security.xsuaa.Assertions.*;
-
-public class CFOAuth2ServiceConfiguration implements OAuth2ServiceConfiguration {
-
-	private final Service service;
-	private final Map<String, String> credentials;
-	private final Map<String, String> configuration;
-	private Plan plan; // lazy read
-
-	CFOAuth2ServiceConfiguration(Service service, Map<String, String> configuration, Map<String, String> credentials) {
-		assertNotNull(configuration, "configuration must not be null");
-		assertNotNull(credentials, "credentials must not be null");
-
-		this.service = service;
-		this.configuration = configuration;
-		this.credentials = credentials;
-	}
-
-	@Override
-	public String getClientId() {
-		return credentials.get(CLIENT_ID);
-	}
-
-	@Override
-	public String getClientSecret() {
-		return credentials.get(CLIENT_SECRET);
-	}
-
-	@Override
-	public URI getUrl() {
-		return URI.create(credentials.get(URL));
-	}
-
-	@Override
-	@Nullable
-	public String getProperty(String name) {
-		return credentials.get(name);
-	}
-
-	@Override
-	public boolean hasProperty(String name) {
-		return credentials.containsKey(name);
-	}
-
-	@Override
-	public Service getService() {
-		return this.service;
-	}
-
-	/**
-	 * Cloud Foundry specific information.
-	 * 
-	 * @return the CF service plan.
-	 */
-	public Plan getPlan() {
-		if (plan == null) {
-			String planAsString = configuration.get(SERVICE_PLAN);
-			plan = planAsString != null ? Plan.from(planAsString) : Plan.DEFAULT;
-		}
-		return plan;
-	}
-
-}
diff --git a/java-security/src/main/java/com/sap/cloud/security/json/DefaultJsonObject.java b/java-security/src/main/java/com/sap/cloud/security/json/DefaultJsonObject.java
index be31961a..202450bf 100644
--- a/java-security/src/main/java/com/sap/cloud/security/json/DefaultJsonObject.java
+++ b/java-security/src/main/java/com/sap/cloud/security/json/DefaultJsonObject.java
@@ -7,18 +7,24 @@ import org.json.JSONObject;
 import javax.annotation.Nullable;
 import java.time.DateTimeException;
 import java.time.Instant;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
+import java.util.*;
 
+/**
+ * Use this class to parse a Json String. This might be relevant in case the
+ * {@link com.sap.cloud.security.config.OAuth2ServiceConfiguration} does not
+ * provide all required properties.
+ */
 public class DefaultJsonObject implements JsonObject {
 
 	private final String jsonString;
 	private JSONObject jsonObject;
 
+	/**
+	 * Create an instance
+	 * 
+	 * @param jsonString
+	 *            the content in json format that should be parsed.
+	 */
 	public DefaultJsonObject(String jsonString) {
 		this.jsonString = jsonString;
 	}
@@ -30,7 +36,7 @@ public class DefaultJsonObject implements JsonObject {
 
 	@Override
 	public <T> List<T> getAsList(String name, Class<T> type) {
-		return getJSONArray(name).map(jsonArray -> castToListOfType(jsonArray, type)).orElse(new ArrayList<>());
+		return getJSONArray(name).map(jsonArray -> castToListOfType(jsonArray, type)).orElse(Collections.emptyList());
 	}
 
 	@Override
diff --git a/java-security/src/main/java/com/sap/cloud/security/json/JsonObject.java b/java-security/src/main/java/com/sap/cloud/security/json/JsonObject.java
index f58cf518..81751480 100644
--- a/java-security/src/main/java/com/sap/cloud/security/json/JsonObject.java
+++ b/java-security/src/main/java/com/sap/cloud/security/json/JsonObject.java
@@ -78,7 +78,7 @@ public interface JsonObject {
 	JsonObject getJsonObject(String name);
 
 	/**
-	 * Returns a nested array of JSON objects as list of @{link JsonObject}
+	 * Returns a nested array of JSON objects as list of {@link JsonObject}
 	 * instances. If the property with the given name is not found, an empty list is
 	 * returned.
 	 * 
@@ -94,13 +94,15 @@ public interface JsonObject {
 	/**
 	 * Returns a key-value map of the JSON properties.
 	 *
-	 * Example: {@code
+	 * Example:
+	 * <pre>
+	 * {@code
 	 * String vcapServices = System.getenv(CFConstants.VCAP_SERVICES);
 	 * JsonObject serviceJsonObject = new DefaultJsonObject(vcapServices).getJsonObjects(Service.XSUAA.getCFName()).get(0);
 	 * Map<String, String> xsuaaConfigMap = serviceJsonObject.getKeyValueMap();
 	 * Map<String, String> credentialsMap = serviceJsonObject.getJsonObject(CFConstants.CREDENTIALS).getKeyValueMap();
 	 * }
-	 * 
+	 * </pre>
 	 * @return the json properties as key-value map
 	 */
 	Map<String, String> getKeyValueMap();
diff --git a/java-security/src/main/java/com/sap/cloud/security/json/JsonParsingException.java b/java-security/src/main/java/com/sap/cloud/security/json/JsonParsingException.java
index baf55277..cfa6a1f1 100644
--- a/java-security/src/main/java/com/sap/cloud/security/json/JsonParsingException.java
+++ b/java-security/src/main/java/com/sap/cloud/security/json/JsonParsingException.java
@@ -1,5 +1,8 @@
 package com.sap.cloud.security.json;
 
+/**
+ * An extraordinary runtime exception during json parsing.
+ */
 public class JsonParsingException extends RuntimeException {
 
 	public JsonParsingException(String message) {
diff --git a/java-security/src/main/java/com/sap/cloud/security/servlet/AbstractTokenAuthenticator.java b/java-security/src/main/java/com/sap/cloud/security/servlet/AbstractTokenAuthenticator.java
index 201fa719..08ad6355 100644
--- a/java-security/src/main/java/com/sap/cloud/security/servlet/AbstractTokenAuthenticator.java
+++ b/java-security/src/main/java/com/sap/cloud/security/servlet/AbstractTokenAuthenticator.java
@@ -1,9 +1,11 @@
 package com.sap.cloud.security.servlet;
 
+import com.sap.cloud.security.config.OAuth2ServiceConfiguration;
 import com.sap.cloud.security.token.SecurityContext;
 import com.sap.cloud.security.token.Token;
 import com.sap.cloud.security.token.validation.ValidationResult;
 import com.sap.cloud.security.token.validation.Validator;
+import com.sap.cloud.security.token.validation.validators.JwtValidatorBuilder;
 import com.sap.cloud.security.xsuaa.client.OAuth2TokenKeyServiceWithCache;
 import com.sap.cloud.security.xsuaa.client.OidcConfigurationServiceWithCache;
 import com.sap.cloud.security.xsuaa.http.HttpHeaders;
@@ -23,6 +25,7 @@ public abstract class AbstractTokenAuthenticator implements TokenAuthenticator {
 	private Validator<Token> tokenValidator;
 	protected OidcConfigurationServiceWithCache oidcConfigurationService;
 	protected OAuth2TokenKeyServiceWithCache tokenKeyService;
+	protected OAuth2ServiceConfiguration serviceConfiguration;
 
 	@Override
 	public TokenAuthenticationResult validateRequest(ServletRequest request, ServletResponse response) {
@@ -33,7 +36,7 @@ public abstract class AbstractTokenAuthenticator implements TokenAuthenticator {
 			if (headerIsAvailable(authorizationHeader)) {
 				try {
 					Token token = getTokenExtractor().from(authorizationHeader);
-					ValidationResult result = createTokenValidator().validate(token);
+					ValidationResult result = getOrCreateTokenValidator().validate(token);
 					if (result.isValid()) {
 						SecurityContext.setToken(token);
 						return authenticated(token);
@@ -62,11 +65,25 @@ public abstract class AbstractTokenAuthenticator implements TokenAuthenticator {
 		return this;
 	}
 
-	protected abstract Validator<Token> createTokenValidator();
+	public AbstractTokenAuthenticator withServiceConfiguration(OAuth2ServiceConfiguration serviceConfiguration) {
+		this.serviceConfiguration = serviceConfiguration;
+		return this;
+	}
+
+	/**
+	 * Return configured service configuration or Environments.getCurrent() if not
+	 * configured.
+	 * 
+	 * @return the actual service configuration
+	 */
+	protected abstract OAuth2ServiceConfiguration getServiceConfiguration();
 
-	protected Validator<Token> getOrCreateTokenValidator() {
+	private Validator<Token> getOrCreateTokenValidator() {
 		if (tokenValidator == null) {
-			tokenValidator = createTokenValidator();
+			tokenValidator = JwtValidatorBuilder.getInstance(getServiceConfiguration())
+					.withOAuth2TokenKeyService(tokenKeyService)
+					.withOidcConfigurationService(oidcConfigurationService)
+					.build();
 		}
 		return tokenValidator;
 	}
diff --git a/java-security/src/main/java/com/sap/cloud/security/servlet/IasTokenAuthenticator.java b/java-security/src/main/java/com/sap/cloud/security/servlet/IasTokenAuthenticator.java
index 034c571e..dbdd8083 100644
--- a/java-security/src/main/java/com/sap/cloud/security/servlet/IasTokenAuthenticator.java
+++ b/java-security/src/main/java/com/sap/cloud/security/servlet/IasTokenAuthenticator.java
@@ -1,10 +1,8 @@
 package com.sap.cloud.security.servlet;
 
 import com.sap.cloud.security.config.Environments;
+import com.sap.cloud.security.config.OAuth2ServiceConfiguration;
 import com.sap.cloud.security.token.IasToken;
-import com.sap.cloud.security.token.Token;
-import com.sap.cloud.security.token.validation.Validator;
-import com.sap.cloud.security.token.validation.validators.JwtValidatorBuilder;
 import com.sap.cloud.security.xsuaa.client.OAuth2TokenKeyServiceWithCache;
 import com.sap.cloud.security.xsuaa.client.OidcConfigurationServiceWithCache;
 
@@ -21,12 +19,7 @@ public class IasTokenAuthenticator extends AbstractTokenAuthenticator {
 	}
 
 	@Override
-	protected Validator<Token> createTokenValidator() {
-		return JwtValidatorBuilder
-				.getInstance(Environments.getCurrent().getIasConfiguration())
-				.withOAuth2TokenKeyService(tokenKeyService)
-				.withOidcConfigurationService(oidcConfigurationService)
-				.build();
+	protected OAuth2ServiceConfiguration getServiceConfiguration() {
+		return serviceConfiguration != null ? serviceConfiguration : Environments.getCurrent().getIasConfiguration();
 	}
-
 }
diff --git a/java-security/src/main/java/com/sap/cloud/security/servlet/XsuaaTokenAuthenticator.java b/java-security/src/main/java/com/sap/cloud/security/servlet/XsuaaTokenAuthenticator.java
index 36abd4d9..b64be667 100644
--- a/java-security/src/main/java/com/sap/cloud/security/servlet/XsuaaTokenAuthenticator.java
+++ b/java-security/src/main/java/com/sap/cloud/security/servlet/XsuaaTokenAuthenticator.java
@@ -2,15 +2,11 @@ package com.sap.cloud.security.servlet;
 
 import com.sap.cloud.security.config.Environments;
 import com.sap.cloud.security.config.OAuth2ServiceConfiguration;
-import com.sap.cloud.security.config.Service;
 import com.sap.cloud.security.config.cf.CFConstants;
 import com.sap.cloud.security.token.*;
-import com.sap.cloud.security.token.validation.Validator;
-import com.sap.cloud.security.token.validation.validators.JwtValidatorBuilder;
 import com.sap.cloud.security.xsuaa.client.OAuth2TokenKeyServiceWithCache;
 import com.sap.cloud.security.xsuaa.client.OidcConfigurationServiceWithCache;
 
-import javax.annotation.Nullable;
 import java.util.List;
 
 public class XsuaaTokenAuthenticator extends AbstractTokenAuthenticator {
@@ -28,41 +24,27 @@ public class XsuaaTokenAuthenticator extends AbstractTokenAuthenticator {
 	}
 
 	@Override
-	protected Validator<Token> createTokenValidator() {
-		return JwtValidatorBuilder
-				.getInstance(Environments.getCurrent().getXsuaaConfiguration())
-				.withOAuth2TokenKeyService(tokenKeyService)
-				.withOidcConfigurationService(oidcConfigurationService)
-				.configureAnotherServiceInstance(getOtherXsuaaServiceConfiguration())
-				.build();
+	protected OAuth2ServiceConfiguration getServiceConfiguration() {
+		return serviceConfiguration != null ? serviceConfiguration : Environments.getCurrent().getXsuaaConfiguration();
+	}
+
+	private ScopeConverter getScopeConverter() {
+		return new XsuaaScopeConverter(
+				getServiceConfiguration().getProperty(CFConstants.XSUAA.APP_ID));
 	}
 
 	private class XsuaaTokenExtractor implements TokenExtractor {
 		@Override
 		public Token from(String authorizationHeader) {
-			if (Environments.getCurrent().getXsuaaConfiguration() != null) {
-				return new XsuaaToken(authorizationHeader,
-						Environments.getCurrent().getXsuaaConfiguration().getProperty(CFConstants.XSUAA.APP_ID));
-			}
-			throw new RuntimeException("XsuaaConfiguration not found. Are VCAP_SERVICES missing?");
-		}
-	}
-
-	@Nullable
-	private OAuth2ServiceConfiguration getOtherXsuaaServiceConfiguration() {
-		if (Environments.getCurrent().getNumberOfXsuaaConfigurations() > 1) {
-			return Environments.getCurrent().getXsuaaConfigurationForTokenExchange();
+			return new XsuaaToken(authorizationHeader)
+					.withScopeConverter(getScopeConverter());
 		}
-		return null;
 	}
 
 	@Override
 	protected TokenAuthenticationResult authenticated(Token token) {
-		if (token.getService() != Service.XSUAA) {
-			return super.authenticated(token);
-		}
-		List<String> scopes = token.getClaimAsStringList(TokenClaims.XSUAA.SCOPES);
-		List<String> translatedScopes = new XsuaaScopeTranslator().translateToLocalScope(scopes);
+		List<String> translatedScopes = getScopeConverter()
+				.convert(((XsuaaToken) token).getScopes());
 		return TokenAuthenticationResult.createAuthenticated(translatedScopes, token);
 	}
 
diff --git a/java-security/src/main/java/com/sap/cloud/security/token/AbstractToken.java b/java-security/src/main/java/com/sap/cloud/security/token/AbstractToken.java
index 099806a2..69a4d6b1 100644
--- a/java-security/src/main/java/com/sap/cloud/security/token/AbstractToken.java
+++ b/java-security/src/main/java/com/sap/cloud/security/token/AbstractToken.java
@@ -16,6 +16,10 @@ import java.util.regex.Pattern;
 import static com.sap.cloud.security.token.TokenClaims.EXPIRATION;
 import static com.sap.cloud.security.token.TokenClaims.NOT_BEFORE;
 
+/**
+ * Decodes and parses encoded access token (JWT) Token and provides access to
+ * token header parameters and claims.
+ */
 public abstract class AbstractToken implements Token {
 	private final DefaultJsonObject headerJsonObject;
 	private final DefaultJsonObject payloadJsonObject;
diff --git a/java-security/src/main/java/com/sap/cloud/security/token/ScopeConverter.java b/java-security/src/main/java/com/sap/cloud/security/token/ScopeConverter.java
new file mode 100644
index 00000000..271a988a
--- /dev/null
+++ b/java-security/src/main/java/com/sap/cloud/security/token/ScopeConverter.java
@@ -0,0 +1,7 @@
+package com.sap.cloud.security.token;
+
+import java.util.List;
+
+public interface ScopeConverter {
+	List<String> convert(List<String> scopes);
+}
diff --git a/java-security/src/main/java/com/sap/cloud/security/token/Token.java b/java-security/src/main/java/com/sap/cloud/security/token/Token.java
index 6042cf4a..a2b3d396 100644
--- a/java-security/src/main/java/com/sap/cloud/security/token/Token.java
+++ b/java-security/src/main/java/com/sap/cloud/security/token/Token.java
@@ -9,6 +9,9 @@ import java.security.Principal;
 import java.time.Instant;
 import java.util.List;
 
+/**
+ * Represents a (JWT) access token.
+ */
 public interface Token {
 
 	/**
@@ -60,14 +63,13 @@ public interface Token {
 
 	/**
 	 * Extracts the value as a list of strings for the given claim. If the claim is
-	 * not found, it will return null. If the given calim is not a list of strings,
+	 * not found, it will return null. If the given claim is not a list of strings,
 	 * it will throw a {@link JsonParsingException}.
 	 * 
 	 * @param claimName
 	 *            the name of the claim as defined here {@link TokenClaims}.
-	 * @return the data of the given claim as a list of strings.
+	 * @return the data of the given claim as a list of strings or an empty list.
 	 */
-	@Nullable
 	List<String> getClaimAsStringList(@Nonnull String claimName);
 
 	/**
diff --git a/java-security/src/main/java/com/sap/cloud/security/token/TokenClaims.java b/java-security/src/main/java/com/sap/cloud/security/token/TokenClaims.java
index d9e313aa..211c96a9 100644
--- a/java-security/src/main/java/com/sap/cloud/security/token/TokenClaims.java
+++ b/java-security/src/main/java/com/sap/cloud/security/token/TokenClaims.java
@@ -1,7 +1,7 @@
 package com.sap.cloud.security.token;
 
 /**
- * Class with constants denoting Jwt claims as specified here:
+ * Constants denoting Jwt claims as specified here:
  * https://tools.ietf.org/html/rfc7519#section-4
  */
 public final class TokenClaims {
diff --git a/java-security/src/main/java/com/sap/cloud/security/token/TokenHeader.java b/java-security/src/main/java/com/sap/cloud/security/token/TokenHeader.java
index d2eb2972..0aa003c0 100644
--- a/java-security/src/main/java/com/sap/cloud/security/token/TokenHeader.java
+++ b/java-security/src/main/java/com/sap/cloud/security/token/TokenHeader.java
@@ -1,7 +1,7 @@
 package com.sap.cloud.security.token;
 
 /**
- * Class with constants denoting Jwt header parameters.
+ * Constants denoting Jwt header parameters.
  * https://tools.ietf.org/html/rfc7515#section-4
  */
 public final class TokenHeader {
diff --git a/java-security/src/main/java/com/sap/cloud/security/token/XsuaaScopeConverter.java b/java-security/src/main/java/com/sap/cloud/security/token/XsuaaScopeConverter.java
new file mode 100644
index 00000000..fb341534
--- /dev/null
+++ b/java-security/src/main/java/com/sap/cloud/security/token/XsuaaScopeConverter.java
@@ -0,0 +1,40 @@
+package com.sap.cloud.security.token;
+
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * This utility class translates XSUAA scopes that are specified in global form
+ * and prefixed with the "appId.", to local ones.
+ */
+public class XsuaaScopeConverter implements ScopeConverter {
+	private final Pattern globalScopePattern;
+
+	/**
+	 * Creates an instance of the converter.
+	 * 
+	 * @param appId
+	 *            the xs application name e.g. myapp!t123.
+	 */
+	public XsuaaScopeConverter(String appId) {
+		this.globalScopePattern = Pattern.compile(appId + "\\.(.+)");
+	}
+
+	@Override
+	public List<String> convert(List<String> scopes) {
+		return scopes.stream()
+				.map(this::convertToLocalScope)
+				.collect(Collectors.toList());
+	}
+
+	private String convertToLocalScope(String scope) {
+		Matcher matcher = globalScopePattern.matcher(scope);
+		if (matcher.matches()) {
+			return matcher.group(matcher.groupCount());
+		}
+		return scope;
+	}
+
+}
diff --git a/java-security/src/main/java/com/sap/cloud/security/token/XsuaaScopeTranslator.java b/java-security/src/main/java/com/sap/cloud/security/token/XsuaaScopeTranslator.java
deleted file mode 100644
index 16d4274b..00000000
--- a/java-security/src/main/java/com/sap/cloud/security/token/XsuaaScopeTranslator.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.sap.cloud.security.token;
-
-import java.util.Collection;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-
-/**
- * This utility class translates XSUAA scopes that are specified in global form
- * to local ones.
- */
-public class XsuaaScopeTranslator {
-
-	private static final Pattern GLOBAL_SCOPE_PATTERN = Pattern.compile("[\\w-\\.]+!\\w+\\.(.+)");
-
-	public List<String> translateToLocalScope(Collection<String> scopes) {
-		return scopes.stream()
-				.map(this::convertToLocalScope)
-				.collect(Collectors.toList());
-	}
-
-	private String convertToLocalScope(String scope) {
-		Matcher matcher = GLOBAL_SCOPE_PATTERN.matcher(scope);
-		if (matcher.matches()) {
-			return matcher.group(matcher.groupCount());
-		}
-		return scope;
-	}
-
-}
diff --git a/java-security/src/main/java/com/sap/cloud/security/token/XsuaaToken.java b/java-security/src/main/java/com/sap/cloud/security/token/XsuaaToken.java
index 8613a035..d24e8967 100644
--- a/java-security/src/main/java/com/sap/cloud/security/token/XsuaaToken.java
+++ b/java-security/src/main/java/com/sap/cloud/security/token/XsuaaToken.java
@@ -11,19 +11,46 @@ import java.util.List;
 
 import static com.sap.cloud.security.token.TokenClaims.XSUAA.*;
 
+/**
+ * Decodes and parses encoded access token (JWT) (Token) for the Xsuaa identity
+ * service and provides access to token header parameters and claims.
+ */
 public class XsuaaToken extends AbstractToken {
 	static final String UNIQUE_USER_NAME_FORMAT = "user/%s/%s"; // user/<origin>/<logonName>
 	static final String UNIQUE_CLIENT_NAME_FORMAT = "client/%s"; // client/<clientid>
-	private final String appId;
+	private ScopeConverter scopeConverter;
 
-	public XsuaaToken(@Nonnull DecodedJwt decodedJwt, @Nonnull String appId) {
+	/**
+	 * Creates an instance.
+	 *
+	 * @param decodedJwt
+	 *            the decoded jwt
+	 */
+	public XsuaaToken(@Nonnull DecodedJwt decodedJwt) {
 		super(decodedJwt);
-		this.appId = appId;
 	}
 
-	public XsuaaToken(@Nonnull String accessToken, @Nonnull String appId) {
+	/**
+	 * Creates an instance.
+	 *
+	 * @param accessToken
+	 *            the encoded access token, e.g. from the {@code Authorization}
+	 *            header.
+	 */
+	public XsuaaToken(@Nonnull String accessToken) {
 		super(accessToken);
-		this.appId = appId;
+	}
+
+	/**
+	 * Configures a scope converter, e.g. required for the
+	 * {@link #hasLocalScope(String)}
+	 *
+	 * @param converter
+	 *            the scope converter, e.g. {@link XsuaaScopeConverter}
+	 */
+	public XsuaaToken withScopeConverter(ScopeConverter converter) {
+		this.scopeConverter = converter;
+		return this;
 	}
 
 	/**
@@ -51,6 +78,11 @@ public class XsuaaToken extends AbstractToken {
 		return String.format(UNIQUE_USER_NAME_FORMAT, origin, userLoginName);
 	}
 
+	/**
+	 * Returns the list of the claim "scope".
+	 * 
+	 * @return the list of the claim scope or empty list.
+	 */
 	public List<String> getScopes() {
 		return getClaimAsStringList(TokenClaims.XSUAA.SCOPES);
 	}
@@ -75,12 +107,8 @@ public class XsuaaToken extends AbstractToken {
 		return Service.XSUAA;
 	}
 
-	public String getAppId() {
-		return appId;
-	}
-
 	/**
-	 * Checks if a scope is available in the authentication token.
+	 * Checks if a scope is available in the access token.
 	 * 
 	 * @param scope
 	 *            name of the scope
@@ -91,17 +119,14 @@ public class XsuaaToken extends AbstractToken {
 	}
 
 	/**
-	 * Check if a local scope is available in the authentication token.
+	 * Check if a local scope is available in the authentication token. <br>
 	 *
 	 * @param scope
 	 *            name of local scope (without the appId)
 	 * @return true if local scope is available
 	 **/
 	public boolean hasLocalScope(@Nonnull String scope) {
-		return hasScope(asGlobalScope(scope));
-	}
-
-	private String asGlobalScope(@Nonnull String scope) {
-		return appId + "." + scope;
+		Assertions.assertNotNull(scopeConverter, "scopeConverter must not be null");
+		return scopeConverter.convert(getScopes()).contains(scope);
 	}
 }
diff --git a/java-security/src/main/java/com/sap/cloud/security/token/validation/CombiningValidator.java b/java-security/src/main/java/com/sap/cloud/security/token/validation/CombiningValidator.java
index 1abb3ad5..33016c0e 100644
--- a/java-security/src/main/java/com/sap/cloud/security/token/validation/CombiningValidator.java
+++ b/java-security/src/main/java/com/sap/cloud/security/token/validation/CombiningValidator.java
@@ -49,7 +49,7 @@ public class CombiningValidator<T> implements Validator<T> {
 	/**
 	 * Components that are interested in the result of the token validation can
 	 * register a validation listener which is called whenever a token is validated.
-	 * Listener must implement the @{link ValidationListener} interface.
+	 * Listener must implement the {@link ValidationListener} interface.
 	 *
 	 * @param validationListener
 	 *            the listener to be added.
diff --git a/java-security/src/main/java/com/sap/cloud/security/token/validation/ValidationListener.java b/java-security/src/main/java/com/sap/cloud/security/token/validation/ValidationListener.java
index 20b21eb8..4c270da6 100644
--- a/java-security/src/main/java/com/sap/cloud/security/token/validation/ValidationListener.java
+++ b/java-security/src/main/java/com/sap/cloud/security/token/validation/ValidationListener.java
@@ -3,7 +3,9 @@ package com.sap.cloud.security.token.validation;
 /**
  * Implement this interface to register custom validation listener to the
  * {@link CombiningValidator}. Those classes will be called whenever a token is
- * being validated.
+ * being validated. <br>
+ *
+ * This might be relevant for writing your Audit logs.
  */
 public interface ValidationListener {
 
diff --git a/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtIssuerValidator.java b/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtIssuerValidator.java
index fab894c8..e49ec090 100644
--- a/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtIssuerValidator.java
+++ b/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtIssuerValidator.java
@@ -16,6 +16,10 @@ import com.sap.cloud.security.token.TokenClaims;
 import com.sap.cloud.security.token.validation.ValidationResult;
 import com.sap.cloud.security.token.validation.Validator;
 
+/**
+ * Validates that the jwt access token is issued by a trust worthy identity
+ * service.
+ */
 public class JwtIssuerValidator implements Validator<Token> {
 	private final String domain;
 	protected final Logger logger = LoggerFactory.getLogger(getClass());
diff --git a/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtSignatureValidator.java b/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtSignatureValidator.java
index b16f28bc..557d5e26 100644
--- a/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtSignatureValidator.java
+++ b/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtSignatureValidator.java
@@ -31,18 +31,23 @@ import com.sap.cloud.security.xsuaa.client.OAuth2TokenKeyServiceWithCache;
 import com.sap.cloud.security.xsuaa.client.OidcConfigurationServiceWithCache;
 import com.sap.cloud.security.xsuaa.jwk.JsonWebKeyImpl;
 import com.sap.cloud.security.xsuaa.jwt.JwtSignatureAlgorithm;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Validates whether the jwt was signed with the public key of the trust-worthy
- * identity service. - asks the token key service for a set of (cached) json web
- * token keys. - creates a PublicKey for the json web key with the respective id
- * and type. - checks whether the jwt is unchanged and signed with a private key
- * that matches the PublicKey.
+ * identity service. <br>
+ * - asks the token key service for a set of (cached) json web token keys.<br>
+ * - creates a PublicKey for the json web key with the respective id and type.
+ * <br>
+ * - checks whether the jwt is unchanged and signed with a private key that
+ * matches the PublicKey.
  */
 public class JwtSignatureValidator implements Validator<Token> {
 	private final OAuth2TokenKeyServiceWithCache tokenKeyService;
 	private final OidcConfigurationServiceWithCache oidcConfigurationService;
 	private OAuth2ServiceConfiguration configuration;
+	private static final Logger LOGGER = LoggerFactory.getLogger(JwtSignatureValidator.class);
 
 	public JwtSignatureValidator(OAuth2TokenKeyServiceWithCache tokenKeyService,
 			OidcConfigurationServiceWithCache oidcConfigurationService) {
@@ -64,35 +69,27 @@ public class JwtSignatureValidator implements Validator<Token> {
 
 		try {
 			jwksUri = getOrRequestJwksUri(token);
+			String fallbackPublicKey = null;
+			if(configuration != null && configuration.hasProperty("verificationkey")) {
+				fallbackPublicKey = configuration.getProperty("verificationkey");
+			}
 			return validate(token.getAccessToken(),
 					token.getHeaderParameterAsString(ALGORITHM_PARAMETER_NAME),
 					token.getHeaderParameterAsString(KEY_ID_PARAMETER_NAME),
-					jwksUri);
+					jwksUri,
+					fallbackPublicKey);
 		} catch (OAuth2ServiceException | IllegalArgumentException e) {
-			if (configuration != null && configuration.hasProperty("verificationkey")) {
-				try {
-					// default Fallback
-					PublicKey publicKey = JsonWebKeyImpl.createPublicKeyFromPemEncodedPublicKey(
-							JwtSignatureAlgorithm.RS256, configuration.getProperty("verificationkey"));
-					return Validation.validateTokenSignature(token.getAccessToken(), publicKey,
-							Signature.getInstance(JwtSignatureAlgorithm.RS256.javaSignature()));
-				} catch (NoSuchAlgorithmException | InvalidKeySpecException ex) {
-					return createInvalid(
-							"Error occurred during signature validation: ({}). Fallback with configured verificationkey was not successful.",
-							e.getMessage());
-				}
-			}
 			return createInvalid("Error occurred during jwks uri determination: {}.", e.getMessage());
 		}
 	}
 
 	// for testing
-	ValidationResult validate(String token, String tokenAlgorithm, @Nullable String tokenKeyId, String tokenKeysUrl) {
+	ValidationResult validate(String token, String tokenAlgorithm, @Nullable String tokenKeyId, String tokenKeysUrl, String fallbackPublicKey) {
 		assertHasText(token, "token must not be null or empty.");
 		assertHasText(tokenKeysUrl, "tokenKeysUrl must not be null or empty.");
 
 		return Validation.getInstance().validate(tokenKeyService, token, tokenAlgorithm, tokenKeyId,
-				URI.create(tokenKeysUrl));
+				URI.create(tokenKeysUrl), fallbackPublicKey);
 	}
 
 	@Nonnull
@@ -124,7 +121,7 @@ public class JwtSignatureValidator implements Validator<Token> {
 
 		public ValidationResult validate(OAuth2TokenKeyServiceWithCache tokenKeyService, String token,
 				String tokenAlgorithm,
-				@Nullable String tokenKeyId, URI tokenKeysUrl) {
+				@Nullable String tokenKeyId, URI tokenKeysUrl, String fallbackPublicKey) {
 			ValidationResult validationResult;
 
 			validationResult = setJwtAlgorithm(tokenAlgorithm);
@@ -135,9 +132,20 @@ public class JwtSignatureValidator implements Validator<Token> {
 			String keyId = tokenKeyId != null ? tokenKeyId : DEFAULT_KEY_ID;
 			validationResult = setPublicKey(tokenKeyService, keyId, tokenKeysUrl);
 			if (validationResult.isErroneous()) {
-				return validationResult;
+				if (fallbackPublicKey != null) {
+					try {
+						this.publicKey = JsonWebKeyImpl.createPublicKeyFromPemEncodedPublicKey(
+								JwtSignatureAlgorithm.RS256, fallbackPublicKey);
+
+					} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+						return createInvalid(
+								"Error occurred during signature validation: ({}). Fallback with configured verificationkey was not successful.",
+								e.getMessage());
+					}
+				} else {
+					return validationResult;
+				}
 			}
-
 			validationResult = setPublicSignatureForKeyType();
 			if (validationResult.isErroneous()) {
 				return validationResult;
diff --git a/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtTimestampValidator.java b/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtTimestampValidator.java
index 3d0920b4..58a99c4a 100644
--- a/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtTimestampValidator.java
+++ b/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtTimestampValidator.java
@@ -15,8 +15,8 @@ import java.time.temporal.TemporalAmount;
 import java.util.function.Supplier;
 
 /**
- * Checks whether the jwt token is used before the "expiration (exp)" time and
- * if it is used after the "not before (nbf)" time.
+ * Validates whether the jwt access token is used before the "expiration (exp)"
+ * time and if it is used after the "not before (nbf)" time.
  *
  * See specification: https://tools.ietf.org/html/rfc7519#section-4.1.4
  * https://tools.ietf.org/html/rfc7519#section-4.1.5
diff --git a/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtValidatorBuilder.java b/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtValidatorBuilder.java
index 9b467949..f6910ab8 100644
--- a/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtValidatorBuilder.java
+++ b/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtValidatorBuilder.java
@@ -8,10 +8,7 @@ import com.sap.cloud.security.xsuaa.Assertions;
 import com.sap.cloud.security.xsuaa.client.*;
 
 import javax.annotation.Nullable;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Optional;
+import java.util.*;
 
 import static com.sap.cloud.security.config.Service.IAS;
 import static com.sap.cloud.security.config.cf.CFConstants.XSUAA.*;
@@ -19,10 +16,11 @@ import static com.sap.cloud.security.config.cf.CFConstants.XSUAA.APP_ID;
 import static com.sap.cloud.security.config.Service.XSUAA;
 
 /**
- * Class used to build a token validator. Custom validators can be added via
- * {@link #with(Validator)} method.
+ * Class used to build a token validator for a oauth service configuration (@link OAuth2ServiceConfiguration). <br>
+ * Custom validators can be added via {@link #with(Validator)} method.
  */
 public class JwtValidatorBuilder {
+	private static Map<OAuth2ServiceConfiguration, JwtValidatorBuilder> instances = new HashMap<>();
 	private final Collection<Validator<Token>> validators = new ArrayList<>();
 	private OAuth2ServiceConfiguration configuration;
 	private OidcConfigurationServiceWithCache oidcConfigurationService = null;
@@ -34,15 +32,25 @@ public class JwtValidatorBuilder {
 		// use getInstance factory method
 	}
 
+	/**
+	 * Creates a builder instance that can be configured further.
+	 * @param configuration
+	 * 			the identity service configuration
+	 * @return the builder
+	 */
 	public static JwtValidatorBuilder getInstance(OAuth2ServiceConfiguration configuration) {
 		Assertions.assertNotNull(configuration, "configuration must not be null");
-		JwtValidatorBuilder tokenBuilder = new JwtValidatorBuilder();
-		tokenBuilder.configuration = configuration;
-		return tokenBuilder;
+		if (instances.containsKey(configuration)) {
+			return instances.get(configuration);
+		}
+		JwtValidatorBuilder instance = new JwtValidatorBuilder();
+		instance.configuration = configuration;
+		instances.put(configuration, instance);
+		return instance;
 	}
 
 	/**
-	 * Add the validator to the validation chain.
+	 * Adds a custom validator to the validation chain.
 	 *
 	 * @param validator
 	 *            the validator used for validation.
@@ -53,22 +61,58 @@ public class JwtValidatorBuilder {
 		return this;
 	}
 
+	/**
+	 * Sets / overwrites the default audience validator.
+	 *
+	 * @param audienceValidator
+	 *            the validator used for validation.
+	 * @return this builder.
+	 */
 	public JwtValidatorBuilder withAudienceValidator(Validator<Token> audienceValidator) {
 		this.customAudienceValidator = audienceValidator;
 		return this;
 	}
 
+	/**
+	 * Overwrite in case you want to configure your own
+	 * {@link OAuth2TokenKeyServiceWithCache}. For example you like to change the
+	 * cache settings or you like to configure the {@link OAuth2TokenKeyService}
+	 * with your own Rest client.
+	 *
+	 * @param tokenKeyService
+	 *            your token key service
+	 * @return this builder
+	 */
 	public JwtValidatorBuilder withOAuth2TokenKeyService(OAuth2TokenKeyServiceWithCache tokenKeyService) {
 		this.tokenKeyService = tokenKeyService;
 		return this;
 	}
 
+	/**
+	 * Overwrite in case you want to configure your own
+	 * {@link OidcConfigurationServiceWithCache}. For example you like to change the
+	 * cache settings or you like to configure the {@link OidcConfigurationService}
+	 * with your own Rest client.
+	 *
+	 * @param oidcConfigurationService
+	 *            your token key service
+	 * @return this builder
+	 */
 	public JwtValidatorBuilder withOidcConfigurationService(
 			OidcConfigurationServiceWithCache oidcConfigurationService) {
 		this.oidcConfigurationService = oidcConfigurationService;
 		return this;
 	}
 
+	/**
+	 * Allows to provide another service configuration, e.g. in case you have
+	 * multiple Xsuaa identity service instances and you like to accept tokens
+	 * issued for them as well.
+	 *
+	 * @param otherConfiguration
+	 *            the configuration of the other service instance, e.g. the broker
+	 * @return this builder
+	 */
 	public JwtValidatorBuilder configureAnotherServiceInstance(
 			@Nullable OAuth2ServiceConfiguration otherConfiguration) {
 		this.otherConfiguration = otherConfiguration;
@@ -76,6 +120,8 @@ public class JwtValidatorBuilder {
 	}
 
 	/**
+	 * Builds the validators with the applied parameters.
+	 *
 	 * @return the combined validators.
 	 */
 	public CombiningValidator<Token> build() {
diff --git a/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/XsuaaJwtAudienceValidator.java b/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/XsuaaJwtAudienceValidator.java
index 2031ad0c..134ee904 100644
--- a/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/XsuaaJwtAudienceValidator.java
+++ b/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/XsuaaJwtAudienceValidator.java
@@ -20,7 +20,11 @@ import java.util.Map;
 import java.util.stream.Collectors;
 
 /**
- * Validate audience using audience field content. in case this field is empty,
+ * Validates if the jwt access token is intended for the OAuth2 client of this
+ * application. The aud (audience) claim identifies the recipients the JWT is
+ * issued for.
+ *
+ * Validates audience using audience field content. in case this field is empty,
  * the audience is derived from the scope field.
  */
 public class XsuaaJwtAudienceValidator implements Validator<Token> {
diff --git a/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/XsuaaJwtIssuerValidator.java b/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/XsuaaJwtIssuerValidator.java
index 3fbefd1d..0179229f 100644
--- a/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/XsuaaJwtIssuerValidator.java
+++ b/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/XsuaaJwtIssuerValidator.java
@@ -15,6 +15,11 @@ import com.sap.cloud.security.token.TokenHeader;
 import com.sap.cloud.security.token.validation.ValidationResult;
 import com.sap.cloud.security.token.validation.Validator;
 
+/**
+ * Validates that the jwt access token is issued by a trust worthy identity
+ * service. In case of XSUAA does the token key url (jku JWT header parameter)
+ * must match the identity service domain.
+ */
 public class XsuaaJwtIssuerValidator implements Validator<Token> {
 	private final String domain;
 	protected final Logger logger = LoggerFactory.getLogger(getClass());
@@ -37,7 +42,6 @@ public class XsuaaJwtIssuerValidator implements Validator<Token> {
 			return createInvalid(
 					"Issuer validation can not be performed because Jwt token does not contain 'jku' header parameter.");
 		}
-
 		return matchesTokenKeyUrlDomain(tokenKeyUrl);
 	}
 
diff --git a/samples/java-security-usage/pom.xml b/samples/java-security-usage/pom.xml
index c8e0e0a9..ec13bcd5 100755
--- a/samples/java-security-usage/pom.xml
+++ b/samples/java-security-usage/pom.xml
@@ -14,7 +14,6 @@
         <javax.servlet.api.version>3.1.0</javax.servlet.api.version>
         <wiremock.version>2.25.1</wiremock.version>
         <assertj.version>3.13.2</assertj.version>
-        <mockito.version>3.1.0</mockito.version>
         <jetty.version>9.4.24.v20191120</jetty.version>
         <junit.version>4.12</junit.version>
     </properties>
diff --git a/samples/java-security-usage/src/main/java/com/sap/cloud/security/samples/Application.java b/samples/java-security-usage/src/main/java/com/sap/cloud/security/samples/Application.java
index 17dd1b4f..39ec8c67 100644
--- a/samples/java-security-usage/src/main/java/com/sap/cloud/security/samples/Application.java
+++ b/samples/java-security-usage/src/main/java/com/sap/cloud/security/samples/Application.java
@@ -1,7 +1,9 @@
 package com.sap.cloud.security.samples;
 
+import com.sap.cloud.security.config.Environments;
 import com.sap.cloud.security.samples.tmp.JettyTokenAuthenticator;
 import com.sap.cloud.security.servlet.XsuaaTokenAuthenticator;
+import com.sap.cloud.security.token.XsuaaToken;
 import org.eclipse.jetty.annotations.AnnotationConfiguration;
 import org.eclipse.jetty.plus.webapp.EnvConfiguration;
 import org.eclipse.jetty.plus.webapp.PlusConfiguration;
@@ -13,6 +15,8 @@ import org.eclipse.jetty.webapp.*;
 import java.net.URL;
 import java.util.Arrays;
 
+import static com.sap.cloud.security.config.cf.CFConstants.XSUAA.APP_ID;
+
 /**
  * This class is used to start the sample as standalone application which does not rely on
  * a application server runtime.
@@ -32,6 +36,7 @@ public class Application {
 	private static Server createJettyServer() {
 		WebAppContext context = new WebAppContext();
 		ConstraintSecurityHandler security = new ConstraintSecurityHandler();
+		String appId = Environments.getCurrent().getXsuaaConfiguration().getProperty(APP_ID);
 		security.setAuthenticator(new JettyTokenAuthenticator(new XsuaaTokenAuthenticator()));
 		context.setSecurityHandler(security);
 		context.setConfigurations(new Configuration[] {


Test patch:
diff --git a/java-security-test/README.md b/java-security-test/README.md
index a5c7e1a8..6fdc9703 100644
--- a/java-security-test/README.md
+++ b/java-security-test/README.md
@@ -52,20 +52,12 @@ public class HelloJavaServletTest {
 
 	@ClassRule
 	public static SecurityTestRule rule = SecurityTestRule.getInstance(XSUAA)
-			.setPort(8181) // optionally overwrite identity service port (WireMock)
-			.useApplicationServer() // optionally customize application server and (optionally) overwrite port
+			.useApplicationServer() // optionally customize application server, e.g. port
 			.addApplicationServlet(TestServlet.class, "/hi");  // add servlet to be tested to application server
-
-	@BeforeClass
-	public static void prepareTest() throws Exception {
-		oldProperties = System.getProperties();
-		System.setProperty(VCAP_SERVICES, IOUtils.resourceToString("/vcap.json", StandardCharsets.UTF_8));
-		rule.setClientId(Environments.getCurrent().getXsuaaConfiguration().getClientId());
-	}
-
-	@AfterClass
-	public static void restoreProperties() {
-		System.setProperties(oldProperties);
+    
+	@After
+	public void tearDown() {
+		SecurityContext.clearToken();
 	}
 
 	@Test
@@ -77,8 +69,12 @@ public class HelloJavaServletTest {
 	}
 
 	@Test
-	public void request_withValidToken() throws IOException {
-		HttpGet request = createGetRequest(rule.createToken().getBearerAccessToken());
+	public void requestWithValidToken_ok() throws IOException {
+		String jwt = rule.getPreconfiguredJwtGenerator()
+				.withScopes(SecurityTestRule.DEFAULT_APP_ID + ".Read")
+				.createToken()
+				.getBearerAccessToken();
+		HttpGet request = createGetRequest(jwt);
 		try (CloseableHttpResponse response = HttpClients.createDefault().execute(request)) {
 			String responseBody = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
 			assertThat(response.getStatusLine().getStatusCode()).isEqualTo(HttpStatus.SC_OK);
diff --git a/java-security-test/src/main/java/com/sap/cloud/security/test/ApplicationServerOptions.java b/java-security-test/src/main/java/com/sap/cloud/security/test/ApplicationServerOptions.java
index f9de11e6..862f4051 100644
--- a/java-security-test/src/main/java/com/sap/cloud/security/test/ApplicationServerOptions.java
+++ b/java-security-test/src/main/java/com/sap/cloud/security/test/ApplicationServerOptions.java
@@ -1,9 +1,12 @@
 package com.sap.cloud.security.test;
 
+import com.sap.cloud.security.config.OAuth2ServiceConfiguration;
+import com.sap.cloud.security.config.OAuth2ServiceConfigurationBuilder;
 import com.sap.cloud.security.config.Service;
-import com.sap.cloud.security.servlet.IasTokenAuthenticator;
+import com.sap.cloud.security.config.cf.CFConstants;
 import com.sap.cloud.security.servlet.TokenAuthenticator;
 import com.sap.cloud.security.servlet.XsuaaTokenAuthenticator;
+import com.sap.cloud.security.xsuaa.Assertions;
 
 /**
  * This class is used to configure the application server to serve test servlets
@@ -23,16 +26,42 @@ public class ApplicationServerOptions {
 		this.port = port;
 	}
 
+	/**
+	 * Creates an instance of ApplicationServerOptions. Overwrites the application
+	 * id that is required by the XsuaaAudienceValidator.
+	 *
+	 * @param appId
+	 *            the xs application name e.g. myapp!t123.
+	 * @return the application server options.
+	 */
+	public static ApplicationServerOptions forXsuaaService(String appId, String clientId) {
+		Assertions.assertHasText(appId, "xsappname is required by the XsuaaAudienceValidator");
+		Assertions.assertHasText(clientId, "clientId is required by the XsuaaAudienceValidator");
+		return new ApplicationServerOptions(
+				new XsuaaTokenAuthenticator()
+						.withServiceConfiguration(createServiceConfiguration(appId, clientId)));
+	}
+
+	/**
+	 * Creates an instance of ApplicationServerOptions.
+	 *
+	 * @return the application server options.
+	 */
 	public static ApplicationServerOptions forService(Service service) {
+		ApplicationServerOptions instance;
+
 		switch (service) {
 		case XSUAA:
-			return new ApplicationServerOptions(new XsuaaTokenAuthenticator());
-		case IAS:
-			return new ApplicationServerOptions(new IasTokenAuthenticator());
+			instance = forXsuaaService(SecurityTestRule.DEFAULT_APP_ID, SecurityTestRule.DEFAULT_CLIENT_ID);
+			break;
+		/*
+		 * case IAS: instance = new ApplicationServerOptions(new
+		 * IasTokenAuthenticator()); break;
+		 */
 		default:
 			throw new UnsupportedOperationException("Identity Service " + service + " is not yet supported.");
 		}
-
+		return instance;
 	}
 
 	/**
@@ -69,4 +98,12 @@ public class ApplicationServerOptions {
 		return port;
 	}
 
+	private static OAuth2ServiceConfiguration createServiceConfiguration(String appId, String clientId) {
+		return OAuth2ServiceConfigurationBuilder.forService(Service.XSUAA)
+				.withClientId(clientId)
+				.withProperty(CFConstants.XSUAA.APP_ID, appId)
+				.withProperty(CFConstants.XSUAA.UAA_DOMAIN, "localhost")
+				.build();
+	}
+
 }
diff --git a/java-security-test/src/main/java/com/sap/cloud/security/test/JwtGenerator.java b/java-security-test/src/main/java/com/sap/cloud/security/test/JwtGenerator.java
index f7dba94f..197c779b 100644
--- a/java-security-test/src/main/java/com/sap/cloud/security/test/JwtGenerator.java
+++ b/java-security-test/src/main/java/com/sap/cloud/security/test/JwtGenerator.java
@@ -183,7 +183,7 @@ public class JwtGenerator {
 		case IAS:
 			return new IasToken(token);
 		case XSUAA:
-			return new XsuaaToken(token, null);
+			return new XsuaaToken(token);
 		default:
 			throw new UnsupportedOperationException("Identity Service " + service + " is not supported.");
 		}
diff --git a/java-security-test/src/main/java/com/sap/cloud/security/test/SecurityFilter.java b/java-security-test/src/main/java/com/sap/cloud/security/test/SecurityFilter.java
new file mode 100644
index 00000000..97a16a0e
--- /dev/null
+++ b/java-security-test/src/main/java/com/sap/cloud/security/test/SecurityFilter.java
@@ -0,0 +1,29 @@
+package com.sap.cloud.security.test;
+
+import javax.servlet.*;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+class SecurityFilter implements Filter {
+
+	@Override
+	public void init(FilterConfig filterConfig) {
+	}
+
+	@Override
+	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+			throws IOException, ServletException {
+		if (request instanceof HttpServletRequest) {
+			if (((HttpServletResponse) response).getStatus() < 400) {
+				chain.doFilter(request, response);
+			}
+		} else {
+			chain.doFilter(request, response);
+		}
+	}
+
+	@Override
+	public void destroy() {
+	}
+}
diff --git a/java-security-test/src/main/java/com/sap/cloud/security/test/SecurityTestRule.java b/java-security-test/src/main/java/com/sap/cloud/security/test/SecurityTestRule.java
index dd5f62ae..ba608ad3 100644
--- a/java-security-test/src/main/java/com/sap/cloud/security/test/SecurityTestRule.java
+++ b/java-security-test/src/main/java/com/sap/cloud/security/test/SecurityTestRule.java
@@ -7,6 +7,7 @@ import com.sap.cloud.security.token.Token;
 import com.sap.cloud.security.token.TokenClaims;
 import com.sap.cloud.security.token.TokenHeader;
 import com.sap.cloud.security.xsuaa.client.OAuth2ServiceEndpointsProvider;
+import com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants;
 import com.sap.cloud.security.xsuaa.client.XsuaaDefaultEndpoints;
 import org.apache.commons.io.IOUtils;
 import org.eclipse.jetty.annotations.AnnotationConfiguration;
@@ -36,7 +37,9 @@ import static com.sap.cloud.security.xsuaa.client.OidcConfigurationService.DISCO
 
 public class SecurityTestRule extends ExternalResource {
 
-	private static final Logger logger = LoggerFactory.getLogger(SecurityTestRule.class);
+	public static final String DEFAULT_APP_ID = "xsapp!t0815";
+	public static final String DEFAULT_CLIENT_ID = "sb-clientId!t0815";
+	private static final Logger LOGGER = LoggerFactory.getLogger(SecurityTestRule.class);
 	private static final String LOCALHOST_PATTERN = "http://localhost:%d";
 	private final Map<String, ServletHolder> applicationServletsByPath = new HashMap<>();
 	private final List<FilterHolder> applicationServletFilters = new ArrayList<>();
@@ -50,7 +53,8 @@ public class SecurityTestRule extends ExternalResource {
 	private RSAKeys keys;
 	private int wireMockPort = 0;
 	private Service service;
-	private String clientId;
+
+	private String clientId = DEFAULT_CLIENT_ID;
 	private String jwksUrl;
 
 	private SecurityTestRule() {
@@ -154,19 +158,6 @@ public class SecurityTestRule extends ExternalResource {
 		return this;
 	}
 
-	/**
-	 * Overwrites the client id (cid) claim of the token that is being generated. It
-	 * needs to be configured before the {@link #before()} method.
-	 *
-	 * @param clientId
-	 *            the port on which the wire mock service is started.
-	 * @return the rule itself.
-	 */
-	public SecurityTestRule setClientId(String clientId) {
-		this.clientId = clientId;
-		return this;
-	}
-
 	/**
 	 * Overwrites the private/public key pair to be used. The private key is used to
 	 * sign the jwt token. The public key is provided by jwks endpoint (on behalf of
@@ -205,7 +196,9 @@ public class SecurityTestRule extends ExternalResource {
 				.withPrivateKey(keys.getPrivate());
 		switch (service) {
 		case XSUAA:
-			jwtGenerator.withHeaderParameter(TokenHeader.JWKS_URL, jwksUrl);
+			jwtGenerator
+					.withHeaderParameter(TokenHeader.JWKS_URL, jwksUrl)
+					.withClaimValue(TokenClaims.XSUAA.GRANT_TYPE, OAuth2TokenServiceConstants.GRANT_TYPE_USER_TOKEN);
 			break;
 		default:
 			jwtGenerator.withClaimValue(TokenClaims.ISSUER, wireMockRule.baseUrl());
@@ -274,7 +267,7 @@ public class SecurityTestRule extends ExternalResource {
 				applicationServer.stop();
 			}
 		} catch (Exception e) {
-			logger.error("Failed to stop jetty server", e);
+			LOGGER.error("Failed to stop jetty server", e);
 		}
 	}
 
@@ -287,6 +280,9 @@ public class SecurityTestRule extends ExternalResource {
 		applicationServletFilters.forEach((filterHolder) -> servletHandler
 				.addFilterWithMapping(filterHolder, "/*", EnumSet.of(DispatcherType.REQUEST)));
 
+		servletHandler
+				.addFilterWithMapping(new FilterHolder(new SecurityFilter()), "/*", EnumSet.of(DispatcherType.REQUEST));
+
 		applicationServer = new Server(applicationServerOptions.getPort());
 		applicationServer.setHandler(context);
 		applicationServer.start();
@@ -297,10 +293,12 @@ public class SecurityTestRule extends ExternalResource {
 		JettyTokenAuthenticator authenticator = new JettyTokenAuthenticator(
 				applicationServerOptions.getTokenAuthenticator());
 		security.setAuthenticator(authenticator);
+
 		ServletHandler servletHandler = new ServletHandler();
 		security.setHandler(servletHandler);
 		context.setServletHandler(servletHandler);
 		context.setSecurityHandler(security);
+
 		return servletHandler;
 	}
 
diff --git a/java-security-test/src/main/java/com/sap/cloud/security/test/jetty/JettyTokenAuthenticator.java b/java-security-test/src/main/java/com/sap/cloud/security/test/jetty/JettyTokenAuthenticator.java
index ba2d6cf0..9d6a7a92 100644
--- a/java-security-test/src/main/java/com/sap/cloud/security/test/jetty/JettyTokenAuthenticator.java
+++ b/java-security-test/src/main/java/com/sap/cloud/security/test/jetty/JettyTokenAuthenticator.java
@@ -16,6 +16,9 @@ import java.security.Principal;
 import java.util.HashSet;
 import java.util.Set;
 
+/**
+ * Decorates the TokenAuthenticator and adapts it to Jetty.
+ */
 public class JettyTokenAuthenticator implements Authenticator {
 
 	private static final Logger logger = LoggerFactory.getLogger(JettyTokenAuthenticator.class);
diff --git a/java-security-test/src/test/java/com/sap/cloud/security/test/SecurityTestRuleTest.java b/java-security-test/src/test/java/com/sap/cloud/security/test/SecurityTestRuleTest.java
index 5e9b343e..c1dd2a4d 100644
--- a/java-security-test/src/test/java/com/sap/cloud/security/test/SecurityTestRuleTest.java
+++ b/java-security-test/src/test/java/com/sap/cloud/security/test/SecurityTestRuleTest.java
@@ -12,10 +12,13 @@ import org.apache.http.HttpStatus;
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.impl.client.HttpClients;
+import org.eclipse.jetty.servlet.ServletHolder;
 import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
+import org.mockito.Mockito;
 
+import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -27,6 +30,7 @@ import static com.sap.cloud.security.config.Service.XSUAA;
 import static com.sap.cloud.security.test.ApplicationServerOptions.*;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.any;
 
 public class SecurityTestRuleTest {
 
@@ -54,21 +58,15 @@ public class SecurityTestRuleTest {
 		}
 	}
 
-	@Test
-	public void generatesTokenWithClientId() {
-		Token generatedToken = cut.setClientId("customClientId").createToken();
-		assertThat(generatedToken.getClaimAsString(TokenClaims.XSUAA.CLIENT_ID))
-				.isEqualTo("customClientId");
-	}
-
 	@Test
 	public void generatesTokenWithOtherClaimsAndHeaderParameter() {
-		Token generatedToken = cut.setClientId("customClientId").getPreconfiguredJwtGenerator()
+		Token generatedToken = cut.getPreconfiguredJwtGenerator()
 				.withClaimValue(TokenClaims.ISSUER, "issuer")
-				.withScopes("appId!t123.scope1")
+				.withScopes(SecurityTestRule.DEFAULT_APP_ID + ".scope1")
 				.withHeaderParameter(TokenHeader.TYPE, "type").createToken();
 
-		assertThat(generatedToken.getClaimAsString(TokenClaims.XSUAA.CLIENT_ID)).isEqualTo("customClientId");
+		assertThat(generatedToken.getClaimAsString(TokenClaims.XSUAA.CLIENT_ID))
+				.isEqualTo(SecurityTestRule.DEFAULT_CLIENT_ID);
 		assertThat(generatedToken.getClaimAsStringList(TokenClaims.XSUAA.SCOPES).size()).isEqualTo(1);
 		assertThat(generatedToken.getClaimAsString(TokenClaims.ISSUER)).isEqualTo("issuer");
 		assertThat(generatedToken.getHeaderParameterAsString(TokenHeader.TYPE)).isEqualTo("type");
@@ -95,6 +93,26 @@ public class SecurityTestRuleTest {
 				.collect(Collectors.joining());
 	}
 
+	public static class TestRuleWithMockServlet {
+
+		private HttpServlet mockServlet = Mockito.mock(HttpServlet.class);
+
+		@Rule
+		public SecurityTestRule mockServletRule = SecurityTestRule.getInstance(XSUAA)
+				.useApplicationServer()
+				.addApplicationServlet(new ServletHolder(mockServlet), "/");
+
+		@Test
+		public void testThatServletMethodIsNotCalled() throws ServletException, IOException {
+			HttpGet httpGet = new HttpGet(mockServletRule.getApplicationServerUri());
+			try (CloseableHttpResponse response = HttpClients.createDefault().execute(httpGet)) {
+				assertThat(response.getStatusLine().getStatusCode()).isEqualTo(HttpStatus.SC_UNAUTHORIZED); // 401
+			}
+			Mockito.verify(mockServlet, Mockito.times(0)).service(any(), any());
+		}
+
+	}
+
 	public static class SecurityTestRuleTestWithoutApplicationServer {
 
 		@Rule
diff --git a/java-security/src/test/java/com/sap/cloud/security/config/OAuth2ServiceConfigurationBuilderTest.java b/java-security/src/test/java/com/sap/cloud/security/config/OAuth2ServiceConfigurationBuilderTest.java
new file mode 100644
index 00000000..1964163d
--- /dev/null
+++ b/java-security/src/test/java/com/sap/cloud/security/config/OAuth2ServiceConfigurationBuilderTest.java
@@ -0,0 +1,73 @@
+package com.sap.cloud.security.config;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.net.URI;
+
+import static com.sap.cloud.security.config.cf.CFConstants.URL;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class OAuth2ServiceConfigurationBuilderTest {
+
+	private OAuth2ServiceConfigurationBuilder cut;
+
+	@Before
+	public void setUp() {
+		cut = OAuth2ServiceConfigurationBuilder.forService(Service.XSUAA);
+	}
+
+	@Test
+	public void forService() {
+		OAuth2ServiceConfiguration configuration = cut.build();
+
+		assertThat(configuration.getService()).isEqualTo(Service.XSUAA);
+	}
+
+	@Test
+	public void withClientId() {
+		String clientId = "theClientId";
+
+		OAuth2ServiceConfiguration configuration = cut.withClientId(clientId).build();
+
+		assertThat(configuration.getClientId()).isEqualTo(clientId);
+	}
+
+	@Test
+	public void withClientSecret() {
+		String clientSecret = "theClientSecret";
+
+		OAuth2ServiceConfiguration configuration = cut.withClientSecret(clientSecret).build();
+
+		assertThat(configuration.getClientSecret()).isEqualTo(clientSecret);
+	}
+
+	@Test
+	public void withUrl() {
+		String url = "http://theUrl.org";
+
+		OAuth2ServiceConfiguration configuration = cut.withUrl(url).build();
+
+		assertThat(configuration.getUrl()).isEqualTo(URI.create(url));
+	}
+
+	@Test
+	public void withProperty() {
+		String propertyName = "propertyName";
+		String propertyValue = "value";
+
+		OAuth2ServiceConfiguration configuration = cut.withProperty(propertyName, propertyValue).build();
+
+		assertThat(configuration.getProperty(propertyName)).isEqualTo(propertyValue);
+	}
+
+	@Test
+	public void withUrl_setViaProperty() {
+		String url = "http://theUrl.org";
+
+		OAuth2ServiceConfiguration configuration = cut.withProperty(URL, url).build();
+
+		assertThat(configuration.getUrl()).isEqualTo(URI.create(url));
+	}
+
+}
\ No newline at end of file
diff --git a/java-security/src/test/java/com/sap/cloud/security/config/cf/CFOAuth2ServiceConfigurationTest.java b/java-security/src/test/java/com/sap/cloud/security/config/cf/CFEnvParserTest.java
similarity index 82%
rename from java-security/src/test/java/com/sap/cloud/security/config/cf/CFOAuth2ServiceConfigurationTest.java
rename to java-security/src/test/java/com/sap/cloud/security/config/cf/CFEnvParserTest.java
index 2a738299..6728dc56 100644
--- a/java-security/src/test/java/com/sap/cloud/security/config/cf/CFOAuth2ServiceConfigurationTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/config/cf/CFEnvParserTest.java
@@ -1,5 +1,6 @@
 package com.sap.cloud.security.config.cf;
 
+import com.sap.cloud.security.config.OAuth2ServiceConfiguration;
 import com.sap.cloud.security.config.Service;
 import com.sap.cloud.security.json.DefaultJsonObject;
 import com.sap.cloud.security.json.JsonObject;
@@ -14,11 +15,11 @@ import static com.sap.cloud.security.config.cf.CFConstants.Plan.BROKER;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.assertj.core.api.Assertions.assertThat;
 
-public class CFOAuth2ServiceConfigurationTest {
+public class CFEnvParserTest {
 
-	private CFOAuth2ServiceConfiguration cut;
+	private OAuth2ServiceConfiguration cut;
 
-	public CFOAuth2ServiceConfigurationTest() throws IOException {
+	public CFEnvParserTest() throws IOException {
 		String vcapXsuaa = IOUtils.resourceToString("/vcapXsuaaServiceSingleBinding.json", UTF_8);
 
 		JsonObject serviceJsonObject = new DefaultJsonObject(vcapXsuaa).getJsonObjects(Service.XSUAA.getCFName())
@@ -53,7 +54,8 @@ public class CFOAuth2ServiceConfigurationTest {
 
 	@Test
 	public void getPlan() {
-		assertThat(cut.getPlan()).isEqualTo(BROKER);
+		assertThat(cut.getProperty(SERVICE_PLAN)).isEqualTo("broker");
+		assertThat(Plan.from(cut.getProperty(SERVICE_PLAN))).isEqualTo(BROKER);
 	}
 
 	@Test
diff --git a/java-security/src/test/java/com/sap/cloud/security/config/cf/CFEnvironmentTest.java b/java-security/src/test/java/com/sap/cloud/security/config/cf/CFEnvironmentTest.java
index a2ea99c5..12050b4e 100644
--- a/java-security/src/test/java/com/sap/cloud/security/config/cf/CFEnvironmentTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/config/cf/CFEnvironmentTest.java
@@ -5,7 +5,6 @@ import com.sap.cloud.security.config.OAuth2ServiceConfiguration;
 import com.sap.cloud.security.config.Service;
 import com.sap.cloud.security.json.DefaultJsonObject;
 import com.sap.cloud.security.json.JsonObject;
-import com.sap.cloud.security.token.TokenClaims;
 
 import org.apache.commons.io.IOUtils;
 import org.junit.Before;
@@ -14,6 +13,8 @@ import org.junit.Test;
 import java.io.IOException;
 import java.util.Map;
 
+import static com.sap.cloud.security.config.cf.CFConstants.*;
+import static com.sap.cloud.security.config.cf.CFConstants.SERVICE_PLAN;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.assertj.core.api.Assertions.assertThat;
 
@@ -47,11 +48,11 @@ public class CFEnvironmentTest {
 		JsonObject serviceJsonObject = new DefaultJsonObject(vcapServices).getJsonObjects(Service.XSUAA.getCFName())
 				.get(0);
 		Map<String, String> xsuaaConfigMap = serviceJsonObject.getKeyValueMap();
-		Map<String, String> credentialsMap = serviceJsonObject.getJsonObject(CFConstants.CREDENTIALS).getKeyValueMap();
+		Map<String, String> credentialsMap = serviceJsonObject.getJsonObject(CREDENTIALS).getKeyValueMap();
 
 		assertThat(xsuaaConfigMap.size()).isEqualTo(4);
 		assertThat(credentialsMap.size()).isEqualTo(10);
-		assertThat(credentialsMap.get(CFConstants.CLIENT_SECRET)).isEqualTo("secret");
+		assertThat(credentialsMap.get(CLIENT_SECRET)).isEqualTo("secret");
 	}
 
 	// TODO IAS
@@ -76,7 +77,7 @@ public class CFEnvironmentTest {
 		assertThat(cut.getXsuaaConfiguration().getService()).isEqualTo(Service.XSUAA);
 		assertThat(cut.getXsuaaConfiguration().getClientId()).isEqualTo("xs2.usertoken");
 		assertThat(cut.getXsuaaConfiguration().getClientSecret()).isEqualTo("secret");
-		assertThat(cut.getXsuaaConfiguration().getProperty(CFConstants.XSUAA.UAA_DOMAIN)).isEqualTo("auth.com");
+		assertThat(cut.getXsuaaConfiguration().getProperty(XSUAA.UAA_DOMAIN)).isEqualTo("auth.com");
 		assertThat(cut.getXsuaaConfiguration().getUrl().toString()).isEqualTo("https://paastenant.auth.com");
 
 		assertThat(cut.getNumberOfXsuaaConfigurations()).isEqualTo(1);
@@ -90,16 +91,15 @@ public class CFEnvironmentTest {
 		cut = CFEnvironment.getInstance((str) -> vcapMultipleXsuaa, (str) -> null);
 
 		assertThat(cut.getNumberOfXsuaaConfigurations()).isEqualTo(2);
-		CFOAuth2ServiceConfiguration appServConfig = (CFOAuth2ServiceConfiguration) cut.getXsuaaConfiguration();
-		CFOAuth2ServiceConfiguration brokerServConfig = (CFOAuth2ServiceConfiguration) cut
-				.getXsuaaConfigurationForTokenExchange();
+		OAuth2ServiceConfiguration appServConfig = cut.getXsuaaConfiguration();
+		OAuth2ServiceConfiguration brokerServConfig = cut.getXsuaaConfigurationForTokenExchange();
 
 		assertThat(appServConfig.getService()).isEqualTo(Service.XSUAA);
-		assertThat(appServConfig.getPlan()).isEqualTo(CFConstants.Plan.APPLICATION);
+		assertThat(Plan.from(appServConfig.getProperty(SERVICE_PLAN))).isEqualTo(Plan.APPLICATION);
 
 		assertThat(brokerServConfig).isNotEqualTo(appServConfig);
 		assertThat(brokerServConfig.getService()).isEqualTo(Service.XSUAA);
-		assertThat(brokerServConfig.getPlan()).isEqualTo(CFConstants.Plan.BROKER);
+		assertThat(Plan.from(brokerServConfig.getProperty(SERVICE_PLAN))).isEqualTo(Plan.BROKER);
 		assertThat(brokerServConfig).isSameAs(cut.getXsuaaConfigurationForTokenExchange());
 	}
 
@@ -107,15 +107,15 @@ public class CFEnvironmentTest {
 	public void getConfigurationByPlan() {
 		cut = CFEnvironment.getInstance((str) -> vcapMultipleXsuaa, (str) -> null);
 
-		CFOAuth2ServiceConfiguration appServConfig = (CFOAuth2ServiceConfiguration) cut.loadByPlan(Service.XSUAA,
-				CFConstants.Plan.APPLICATION);
-		CFOAuth2ServiceConfiguration brokerServConfig = (CFOAuth2ServiceConfiguration) cut.loadByPlan(Service.XSUAA,
-				CFConstants.Plan.BROKER);
+		OAuth2ServiceConfiguration appServConfig = cut.loadForServicePlan(Service.XSUAA,
+				Plan.APPLICATION);
+		OAuth2ServiceConfiguration brokerServConfig = cut.loadForServicePlan(Service.XSUAA,
+				Plan.BROKER);
 
-		assertThat(appServConfig.getPlan()).isEqualTo(CFConstants.Plan.APPLICATION);
+		assertThat(Plan.from(appServConfig.getProperty(SERVICE_PLAN))).isEqualTo(Plan.APPLICATION);
 		assertThat(appServConfig).isSameAs(cut.getXsuaaConfiguration());
 
-		assertThat(brokerServConfig.getPlan()).isEqualTo(CFConstants.Plan.BROKER);
+		assertThat(Plan.from(brokerServConfig.getProperty(SERVICE_PLAN))).isEqualTo(Plan.BROKER);
 		assertThat(brokerServConfig).isSameAs(cut.getXsuaaConfigurationForTokenExchange());
 	}
 
diff --git a/java-security/src/test/java/com/sap/cloud/security/servlet/AbstractTokenAuthenticatorTest.java b/java-security/src/test/java/com/sap/cloud/security/servlet/AbstractTokenAuthenticatorTest.java
deleted file mode 100644
index 1b78692a..00000000
--- a/java-security/src/test/java/com/sap/cloud/security/servlet/AbstractTokenAuthenticatorTest.java
+++ /dev/null
@@ -1,95 +0,0 @@
-package com.sap.cloud.security.servlet;
-
-import com.sap.cloud.security.config.Service;
-import com.sap.cloud.security.token.SecurityContext;
-import com.sap.cloud.security.token.Token;
-import com.sap.cloud.security.token.validation.ValidationResult;
-import com.sap.cloud.security.token.validation.ValidationResults;
-import com.sap.cloud.security.token.validation.Validator;
-import com.sap.cloud.security.xsuaa.http.HttpHeaders;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mockito;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import static com.sap.cloud.security.servlet.TokenAuthenticator.TokenExtractor;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.when;
-
-public class AbstractTokenAuthenticatorTest {
-
-	private static final Token TOKEN = Mockito.mock(Token.class);
-
-	private HttpServletResponse httpResponse;
-	private HttpServletRequest httpRequest;
-	private TokenAuthenticator cut;
-
-	@Before
-	public void setUp() {
-		httpRequest = Mockito.mock(HttpServletRequest.class);
-		httpResponse = Mockito.mock(HttpServletResponse.class);
-		cut = createAuthenticator(ValidationResults.createValid());
-		when(TOKEN.getService()).thenReturn(Service.XSUAA);
-	}
-
-	@Test
-	public void defaultConstructor() {
-		cut = new XsuaaTokenAuthenticator();
-	}
-
-	@Test
-	public void doFilter_noHeader_isUnauthenticated() {
-		TokenAuthenticationResult response = cut.validateRequest(httpRequest, httpResponse);
-
-		assertThat(response.isAuthenticated()).isFalse();
-		assertThat(response.getUnauthenticatedReason()).contains("Authorization header is missing");
-	}
-
-	@Test
-	public void doFilter_invalidToken_isUnauthenticated() {
-		mockAuthorizationHeader();
-		String errorMessage = "Token is not valid";
-		cut = createAuthenticator((ValidationResults.createInvalid(errorMessage)));
-
-		TokenAuthenticationResult response = cut.validateRequest(httpRequest, httpResponse);
-
-		assertThat(response.isAuthenticated()).isFalse();
-		assertThat(response.getUnauthenticatedReason()).contains(errorMessage);
-	}
-
-	@Test
-	public void doFilter_validToken_containedInSecurityContext() {
-		mockAuthorizationHeader();
-
-		cut = createAuthenticator(ValidationResults.createValid());
-
-		TokenAuthenticationResult response = cut.validateRequest(httpRequest, httpResponse);
-
-		assertThat(response.isAuthenticated()).isTrue();
-		assertThat(response.getToken()).isSameAs(SecurityContext.getToken());
-	}
-
-	private void mockAuthorizationHeader() {
-		when(httpRequest.getHeader(HttpHeaders.AUTHORIZATION)).thenReturn("Bearer fake token");
-	}
-
-	private TokenAuthenticator createAuthenticator(ValidationResult validationResult) {
-		TokenExtractor tokenExtractor = (header) -> TOKEN;
-		Validator<Token> tokenValidator = (TOKEN) -> validationResult;
-		return new AbstractTokenAuthenticator() {
-
-			@Override
-			public TokenExtractor getTokenExtractor() {
-				return tokenExtractor;
-			}
-
-			@Override
-			protected Validator<Token> createTokenValidator() {
-				return tokenValidator;
-			}
-		};
-	}
-
-}
diff --git a/java-security/src/test/java/com/sap/cloud/security/servlet/XsuaaTokenAuthenticatorTest.java b/java-security/src/test/java/com/sap/cloud/security/servlet/XsuaaTokenAuthenticatorTest.java
new file mode 100644
index 00000000..448384cd
--- /dev/null
+++ b/java-security/src/test/java/com/sap/cloud/security/servlet/XsuaaTokenAuthenticatorTest.java
@@ -0,0 +1,99 @@
+package com.sap.cloud.security.servlet;
+
+import com.sap.cloud.security.config.OAuth2ServiceConfiguration;
+import com.sap.cloud.security.config.OAuth2ServiceConfigurationBuilder;
+import com.sap.cloud.security.config.Service;
+import com.sap.cloud.security.config.cf.CFConstants;
+import com.sap.cloud.security.token.SecurityContext;
+import com.sap.cloud.security.token.XsuaaToken;
+import com.sap.cloud.security.xsuaa.client.OAuth2ServiceException;
+import com.sap.cloud.security.xsuaa.client.OAuth2TokenKeyService;
+import com.sap.cloud.security.xsuaa.client.OAuth2TokenKeyServiceWithCache;
+import com.sap.cloud.security.xsuaa.http.HttpHeaders;
+import com.sap.cloud.security.xsuaa.jwk.JsonWebKeySetFactory;
+import org.apache.commons.io.IOUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+public class XsuaaTokenAuthenticatorTest {
+
+	private final static HttpServletResponse HTTP_RESPONSE = Mockito.mock(HttpServletResponse.class);
+
+	private XsuaaToken xsuaaToken;
+	private OAuth2TokenKeyService tokenKeyService;
+	private OAuth2ServiceConfiguration oAuth2ServiceConfiguration;
+
+	private AbstractTokenAuthenticator cut;
+
+	@Before
+	public void setUp() throws IOException {
+		tokenKeyService = Mockito.mock(OAuth2TokenKeyService.class);
+		when(tokenKeyService.retrieveTokenKeys(any())).thenReturn(
+				JsonWebKeySetFactory.createFromJson(IOUtils.resourceToString("/jsonWebTokenKeys.json", UTF_8)));
+		oAuth2ServiceConfiguration = OAuth2ServiceConfigurationBuilder.forService(Service.XSUAA)
+				.withProperty(CFConstants.XSUAA.UAA_DOMAIN, "auth.com")
+				.withProperty(CFConstants.XSUAA.APP_ID, "appId")
+				.withClientId("clientId")
+				.build();
+
+		xsuaaToken = new XsuaaToken(IOUtils.resourceToString("/xsuaaUserAccessTokenRSA256.txt", UTF_8));
+
+		cut = new XsuaaTokenAuthenticator()
+				.withOAuth2TokenKeyService(
+						OAuth2TokenKeyServiceWithCache.getInstance().withTokenKeyService(tokenKeyService))
+				.withServiceConfiguration(oAuth2ServiceConfiguration);
+	}
+
+	@Test
+	public void validateRequest_noHeader_isUnauthenticated() {
+		HttpServletRequest httpRequest = createRequestWithoutToken();
+
+		TokenAuthenticationResult response = cut.validateRequest(httpRequest, HTTP_RESPONSE);
+
+		assertThat(response.isAuthenticated()).isFalse();
+		assertThat(response.getUnauthenticatedReason()).contains("Authorization header is missing");
+	}
+
+	@Test
+	public void validateRequest_invalidToken_isUnauthenticated() {
+		String errorMessage = "JWT token does not consist of 'header'.'payload'.'signature'";
+		HttpServletRequest httpRequest = createRequestWithToken("Bearer invalid");
+
+		TokenAuthenticationResult response = cut.validateRequest(httpRequest, HTTP_RESPONSE);
+
+		assertThat(response.isAuthenticated()).isFalse();
+		assertThat(response.getUnauthenticatedReason()).contains(errorMessage);
+	}
+
+	@Test
+	public void validateRequest_validToken_containedInSecurityContext() {
+		HttpServletRequest httpRequest = createRequestWithToken(xsuaaToken.getBearerAccessToken());
+
+		TokenAuthenticationResult response = cut.validateRequest(httpRequest, HTTP_RESPONSE);
+
+		assertThat(response.getUnauthenticatedReason()).isEmpty();
+		assertThat(response.isAuthenticated()).isTrue();
+		assertThat(response.getToken()).isSameAs(SecurityContext.getToken());
+	}
+
+	private HttpServletRequest createRequestWithoutToken() {
+		return Mockito.mock(HttpServletRequest.class);
+	}
+
+	private HttpServletRequest createRequestWithToken(String bearerAuthorizationHeader) {
+		HttpServletRequest httpRequest = createRequestWithoutToken();
+		when(httpRequest.getHeader(HttpHeaders.AUTHORIZATION)).thenReturn(bearerAuthorizationHeader);
+		return httpRequest;
+	}
+
+}
diff --git a/java-security/src/test/java/com/sap/cloud/security/token/AbstractTokenTest.java b/java-security/src/test/java/com/sap/cloud/security/token/AbstractTokenTest.java
index 90f30620..63434b67 100644
--- a/java-security/src/test/java/com/sap/cloud/security/token/AbstractTokenTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/token/AbstractTokenTest.java
@@ -7,6 +7,8 @@ import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.security.Principal;
 import java.time.Instant;
+import java.util.Collection;
+import java.util.Collections;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.when;
@@ -44,6 +46,11 @@ public class AbstractTokenTest {
 		assertThat(cut.getClaimAsStringList("aud")).containsExactly("uaa", "sap_osb");
 	}
 
+	@Test
+	public void getClaimAsStringList_unknownClaim_emptyList() {
+		assertThat(cut.getClaimAsStringList("anything")).isEqualTo(Collections.emptyList());
+	}
+
 	@Test
 	public void getExpiration() {
 		assertThat(cut.getExpiration()).isEqualTo(Instant.ofEpochSecond(1572060769L));
diff --git a/java-security/src/test/java/com/sap/cloud/security/token/ScopeTranslatorTest.java b/java-security/src/test/java/com/sap/cloud/security/token/ScopeTranslatorTest.java
deleted file mode 100644
index 8820377d..00000000
--- a/java-security/src/test/java/com/sap/cloud/security/token/ScopeTranslatorTest.java
+++ /dev/null
@@ -1,65 +0,0 @@
-package com.sap.cloud.security.token;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.List;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.util.Lists.newArrayList;
-
-public class ScopeTranslatorTest {
-
-	private XsuaaScopeTranslator cut;
-
-	@Before
-	public void setUp() {
-		cut = new XsuaaScopeTranslator();
-	}
-
-	@Test
-	public void translateToLocalScope() {
-		List<String> scope = newArrayList("myAppId!t1785.Read");
-
-		List<String> translatedScope = cut.translateToLocalScope(scope);
-
-		assertThat(translatedScope).containsExactly("Read");
-	}
-
-	@Test
-	public void translateToLocalScope_doesNotTouchLocalScopedEntries() {
-		List<String> scope = newArrayList("myAppId!t1785.Read", "Display");
-
-		List<String> translatedScope = cut.translateToLocalScope(scope);
-
-		assertThat(translatedScope).containsExactly("Read", "Display");
-	}
-
-	@Test
-	public void translateToLocalScope_nothingToTranslate_returnsSameScope() {
-		List<String> scope = newArrayList("Display");
-
-		List<String> translatedScope = cut.translateToLocalScope(scope);
-
-		assertThat(translatedScope).containsSequence(scope);
-	}
-
-	@Test
-	public void translateToLocalScope_doesNotTouchNonGlobalScopedEntries() {
-		List<String> scope = newArrayList("myAppId.Read", "Display");
-
-		List<String> translatedScope = cut.translateToLocalScope(scope);
-
-		assertThat(translatedScope).containsSequence(scope);
-	}
-
-	@Test
-	public void translateToLocalScope_scopeContainsDotAndUnderscore() {
-		List<String> scope = newArrayList("my_AppId.new!t1785.Read.Context", "sub.myAppid!b13.Write.Context");
-
-		List<String> translatedScope = cut.translateToLocalScope(scope);
-
-		assertThat(translatedScope).containsExactly("Read.Context", "Write.Context");
-	}
-
-}
diff --git a/java-security/src/test/java/com/sap/cloud/security/token/XsuaaScopeConverterTest.java b/java-security/src/test/java/com/sap/cloud/security/token/XsuaaScopeConverterTest.java
new file mode 100644
index 00000000..5dd9be43
--- /dev/null
+++ b/java-security/src/test/java/com/sap/cloud/security/token/XsuaaScopeConverterTest.java
@@ -0,0 +1,74 @@
+package com.sap.cloud.security.token;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.util.Lists.newArrayList;
+
+public class XsuaaScopeConverterTest {
+
+	private XsuaaScopeConverter cut;
+
+	@Before
+	public void setUp() {
+		cut = new XsuaaScopeConverter("myAppId!t1785");
+	}
+
+	@Test
+	public void oneLocalScope() {
+		List<String> scope = newArrayList("myAppId!t1785.Read");
+
+		List<String> translatedScope = cut.convert(scope);
+
+		assertThat(translatedScope).containsExactly("Read");
+	}
+
+	@Test
+	public void doesNotTouchLocalScopedEntries() {
+		List<String> scope = newArrayList("myAppId!t1785.Read", "Display");
+
+		List<String> translatedScope = cut.convert(scope);
+
+		assertThat(translatedScope).containsExactly("Read", "Display");
+	}
+
+	@Test
+	public void nothingToTranslate_returnsSameScope() {
+		List<String> scope = newArrayList("Display");
+
+		List<String> translatedScope = cut.convert(scope);
+
+		assertThat(translatedScope).containsSequence(scope);
+	}
+
+	@Test
+	public void doesNotTouchNonGlobalScopedEntries() {
+		List<String> scope = newArrayList("myAppId.Read", "Display");
+
+		List<String> translatedScope = cut.convert(scope);
+
+		assertThat(translatedScope).containsSequence(scope);
+	}
+
+	@Test
+	public void scopeContainsDotAndUnderscore() {
+		List<String> scope = newArrayList("myAppId!t1785.Read.Context", "myAppId!t1785.Write.Context");
+
+		List<String> translatedScope = cut.convert(scope);
+
+		assertThat(translatedScope).containsExactly("Read.Context", "Write.Context");
+	}
+
+	@Test
+	public void noScopes_emptyList() {
+		List<String> scope = newArrayList();
+
+		List<String> translatedScope = cut.convert(scope);
+
+		assertThat(translatedScope).isEmpty();
+	}
+
+}
diff --git a/java-security/src/test/java/com/sap/cloud/security/token/XsuaaTokenTest.java b/java-security/src/test/java/com/sap/cloud/security/token/XsuaaTokenTest.java
index c1c6dffe..025fec73 100644
--- a/java-security/src/test/java/com/sap/cloud/security/token/XsuaaTokenTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/token/XsuaaTokenTest.java
@@ -1,6 +1,7 @@
 package com.sap.cloud.security.token;
 
 import com.sap.cloud.security.config.Service;
+import com.sap.cloud.security.config.cf.CFConstants;
 import org.apache.commons.io.IOUtils;
 import org.junit.Test;
 
@@ -20,10 +21,10 @@ public class XsuaaTokenTest {
 
 	public XsuaaTokenTest() throws IOException {
 		clientCredentialsToken = new XsuaaToken(
-				IOUtils.resourceToString("/xsuaaCCAccessTokenRSA256.txt", UTF_8), APP_ID);
-		userToken = new XsuaaToken(IOUtils.resourceToString("/xsuaaUserAccessTokenRSA256.txt", UTF_8), APP_ID);
+				IOUtils.resourceToString("/xsuaaCCAccessTokenRSA256.txt", UTF_8));
+		userToken = new XsuaaToken(IOUtils.resourceToString("/xsuaaUserAccessTokenRSA256.txt", UTF_8));
 
-		scopesToken = new XsuaaToken(IOUtils.resourceToString("/xsuaaScopesTokenRSA256.txt", UTF_8), APP_ID);
+		scopesToken = new XsuaaToken(IOUtils.resourceToString("/xsuaaScopesTokenRSA256.txt", UTF_8));
 	}
 
 	@Test
@@ -31,11 +32,6 @@ public class XsuaaTokenTest {
 		assertThat(clientCredentialsToken.getScopes()).containsExactly("ROLE_SERVICEBROKER", "uaa.resource");
 	}
 
-	@Test
-	public void getAppId() {
-		assertThat(clientCredentialsToken.getAppId()).isEqualTo(APP_ID);
-	}
-
 	@Test
 	public void hasScope_scopeExists_isTrue() {
 		assertThat(clientCredentialsToken.hasScope("ROLE_SERVICEBROKER")).isTrue();
@@ -49,8 +45,11 @@ public class XsuaaTokenTest {
 
 	@Test
 	public void hasLocalScope() {
+		scopesToken.withScopeConverter(new XsuaaScopeConverter(APP_ID));
+		assertThat(scopesToken.hasScope(APP_ID + ".scope")).isTrue();
 		assertThat(scopesToken.hasLocalScope("scope")).isTrue();
-		assertThat(scopesToken.hasLocalScope("openid")).isFalse();
+		assertThat(scopesToken.hasScope("openid")).isTrue();
+		assertThat(scopesToken.hasLocalScope("openid")).isTrue();
 	}
 
 	@Test
diff --git a/java-security/src/test/java/com/sap/cloud/security/token/validation/IntegrationTest.java b/java-security/src/test/java/com/sap/cloud/security/token/validation/IntegrationTest.java
index dbdf74dd..d7820fe4 100644
--- a/java-security/src/test/java/com/sap/cloud/security/token/validation/IntegrationTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/token/validation/IntegrationTest.java
@@ -33,7 +33,7 @@ public class IntegrationTest {
 		CombiningValidator<Token> tokenValidator = JwtValidatorBuilder.getInstance(configuration).build();
 
 		Token xsuaaToken = new XsuaaToken(
-				IOUtils.resourceToString("/xsuaaCCAccessTokenRSA256.txt", StandardCharsets.UTF_8), "test-app!t123");
+				IOUtils.resourceToString("/xsuaaCCAccessTokenRSA256.txt", StandardCharsets.UTF_8));
 		ValidationResult result = tokenValidator.validate(xsuaaToken);
 		assertThat(result.isErroneous()).isTrue();
 		assertThat(result.getErrorDescription()).contains("Jwt expired at 2019-10-26T03:32:49Z");
diff --git a/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/JwtSignatureValidatorTest.java b/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/JwtSignatureValidatorTest.java
index 530ac17e..60c17763 100644
--- a/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/JwtSignatureValidatorTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/JwtSignatureValidatorTest.java
@@ -36,12 +36,11 @@ public class JwtSignatureValidatorTest {
 
 	@Before
 	public void setup() throws IOException {
-		xsuaaToken = new XsuaaToken(IOUtils.resourceToString("/xsuaaCCAccessTokenRSA256.txt", UTF_8),
-				APP_ID);
+		xsuaaToken = new XsuaaToken(IOUtils.resourceToString("/xsuaaCCAccessTokenRSA256.txt", UTF_8));
 		iasToken = new IasToken(IOUtils.resourceToString("/iasOidcTokenRSA256.txt", UTF_8));
 
 		xsuaaTokenSignedWithVerificationKey = new XsuaaToken(
-				IOUtils.resourceToString("/xsuaaAccessTokenRSA256_signedWithVerificationKey.txt", UTF_8), APP_ID);
+				IOUtils.resourceToString("/xsuaaAccessTokenRSA256_signedWithVerificationKey.txt", UTF_8));
 
 		endpointsProviderMock = Mockito.mock(OAuth2ServiceEndpointsProvider.class);
 		when(endpointsProviderMock.getJwksUri()).thenReturn(URI.create("https://myoidcprovider.com/jwks_uri"));
@@ -64,7 +63,7 @@ public class JwtSignatureValidatorTest {
 	@Test
 	public void validate_throwsWhenTokenIsNull() {
 		assertThatThrownBy(() -> {
-			cut.validate(null, "key-id-1", "RS256", null);
+			cut.validate(null, "key-id-1", "RS256", null, null);
 		}).isInstanceOf(IllegalArgumentException.class).hasMessageStartingWith("token");
 	}
 
@@ -86,16 +85,24 @@ public class JwtSignatureValidatorTest {
 		OAuth2ServiceConfiguration mockConfiguration = Mockito.mock(OAuth2ServiceConfiguration.class);
 		when(mockConfiguration.hasProperty("verificationkey")).thenReturn(true);
 		when(mockConfiguration.getProperty("verificationkey")).thenReturn(
-				"-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm1QaZzMjtEfHdimrHP3/2Yr+1z685eiOUlwybRVG9i8wsgOUh+PUGuQL8hgulLZWXU5MbwBLTECAEMQbcRTNVTolkq4i67EP6JesHJIFADbK1Ni0KuMcPuiyOLvDKiDEMnYG1XP3X3WCNfsCVT9YoU+lWIrZr/ZsIvQri8jczr4RkynbTBsPaAOygPUlipqDrpadMO1momNCbea/o6GPn38LxEw609ItfgDGhL6f/yVid5pFzZQWb+9l6mCuJww0hnhO6gt6Rv98OWDty9G0frWAPyEfuIW9B+mR/2vGhyU9IbbWpvFXiy9RVbbsM538TCjd5JF2dJvxy24addC4oQIDAQAB-----END PUBLIC KEY-----");
+				"-----BEGIN PUBLIC KEY-----\n" +
+				"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm1QaZzMjtEfHdimrHP3/\n" +
+				"2Yr+1z685eiOUlwybRVG9i8wsgOUh+PUGuQL8hgulLZWXU5MbwBLTECAEMQbcRTN\n" +
+				"VTolkq4i67EP6JesHJIFADbK1Ni0KuMcPuiyOLvDKiDEMnYG1XP3X3WCNfsCVT9Y\n" +
+				"oU+lWIrZr/ZsIvQri8jczr4RkynbTBsPaAOygPUlipqDrpadMO1momNCbea/o6GP\n" +
+				"n38LxEw609ItfgDGhL6f/yVid5pFzZQWb+9l6mCuJww0hnhO6gt6Rv98OWDty9G0\n" +
+				"frWAPyEfuIW9B+mR/2vGhyU9IbbWpvFXiy9RVbbsM538TCjd5JF2dJvxy24addC4\n" +
+				"oQIDAQAB\n" +
+				"-----END PUBLIC KEY-----");
 		cut.withOAuth2Configuration(mockConfiguration);
 		assertThat(cut.validate(xsuaaTokenSignedWithVerificationKey).isValid(), is(true));
 	}
 
 	@Test
-	public void validationFails_whenSignatureOfGeneratedTokenDoesNotMatchVerificationkey() {
+	public void validationFails_whenVerificationkeyIsInvalid() {
 		OAuth2ServiceConfiguration mockConfiguration = Mockito.mock(OAuth2ServiceConfiguration.class);
 		when(mockConfiguration.hasProperty("verificationkey")).thenReturn(true);
-		when(mockConfiguration.getProperty("verificationkey")).thenReturn("INVALID_KEY");
+		when(mockConfiguration.getProperty("verificationkey")).thenReturn("INVALIDKEY");
 		cut.withOAuth2Configuration(mockConfiguration);
 
 		ValidationResult result = cut.validate(xsuaaTokenSignedWithVerificationKey);
@@ -104,6 +111,19 @@ public class JwtSignatureValidatorTest {
 				containsString("Fallback with configured verificationkey was not successful."));
 	}
 
+	@Test
+	public void validationFails_whenSignatureOfGeneratedTokenDoesNotMatchVerificationkey() {
+		OAuth2ServiceConfiguration mockConfiguration = Mockito.mock(OAuth2ServiceConfiguration.class);
+		when(mockConfiguration.hasProperty("verificationkey")).thenReturn(true);
+		when(mockConfiguration.getProperty("verificationkey")).thenReturn("-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm1QaZzMjtEfHdimrHP3/2Yr+1z685eiOUlwybRVG9i8wsgOUh+PUGuQL8hgulLZWXU5MbwBLTECAEMQbcRTNVTolkq4i67EP6JesHJIFADbK1Ni0KuMcPuiyOLvDKiDEMnYG1XP3X3WCNfsCVT9YoU+lWIrZr/ZsIvQri8jczr4RkynbTBsPaAOygPUlipqDrpadMO1momNCbea/o6GPn38LxEw609ItfgDGhL6f/yVid5pFzZQWb+9l6mCuJww0hnhO6gt6Rv98OWDty9G0frWAPyEfuIW9B+mR/3vGhyU9IbbWpvFXiy9RVbbsM538TCjd5JF2dJvxy24addC4oQIDAQAB-----END PUBLIC KEY-----");
+		cut.withOAuth2Configuration(mockConfiguration);
+
+		ValidationResult result = cut.validate(xsuaaTokenSignedWithVerificationKey);
+		assertThat(result.isErroneous(), is(true));
+		assertThat(result.getErrorDescription(),
+				containsString("Signature of Jwt Token is not valid"));
+	}
+
 	@Test
 	public void validationFails_whenJwtPayloadModified() {
 		String[] tokenHeaderPayloadSignature = xsuaaToken.getAccessToken().split(Pattern.quote("."));
@@ -113,7 +133,7 @@ public class JwtSignatureValidatorTest {
 				.append(otherHeaderPayloadSignature[1])
 				.append(".")
 				.append(tokenHeaderPayloadSignature[2]).toString();
-		assertThat(cut.validate(new XsuaaToken(tokenWithOthersSignature, APP_ID)).isErroneous(), is(true));
+		assertThat(cut.validate(new XsuaaToken(tokenWithOthersSignature)).isErroneous(), is(true));
 	}
 
 	@Test
@@ -124,7 +144,7 @@ public class JwtSignatureValidatorTest {
 				.append(tokenHeaderPayloadSignature[1]).toString();
 
 		ValidationResult result = cut.validate(tokenWithOthersSignature, "RS256", "key-id-1",
-				"https://authentication.stagingaws.hanavlab.ondemand.com/token_keys");
+				"https://authentication.stagingaws.hanavlab.ondemand.com/token_keys", null);
 		assertThat(result.isErroneous(), is(true));
 		assertThat(result.getErrorDescription(),
 				containsString("Jwt token does not consist of 'header'.'payload'.'signature'."));
@@ -137,7 +157,7 @@ public class JwtSignatureValidatorTest {
 						IOUtils.resourceToString("/iasJsonWebTokenKeys.json", UTF_8)));
 
 		ValidationResult result = cut.validate(iasToken.getAccessToken(), "RS256", "default-kid-2",
-				"https://myauth.com/jwks_uri");
+				"https://myauth.com/jwks_uri", null);
 		assertThat(result.isErroneous(), is(true));
 		assertThat(result.getErrorDescription(),
 				containsString(
@@ -147,7 +167,7 @@ public class JwtSignatureValidatorTest {
 	@Test
 	public void validationFails_whenTokenAlgorithmIsNotRSA256() {
 		ValidationResult validationResult = cut.validate(xsuaaToken.getAccessToken(), "ES123", "key-id-1",
-				"https://myauth.com/jwks_uri");
+				"https://myauth.com/jwks_uri", null);
 		assertThat(validationResult.isErroneous(), is(true));
 		assertThat(validationResult.getErrorDescription(),
 				startsWith("Jwt token with signature algorithm 'ES123' can not be verified."));
@@ -156,7 +176,7 @@ public class JwtSignatureValidatorTest {
 	@Test
 	public void validationFails_whenTokenAlgorithmIsNull() {
 		ValidationResult validationResult = cut.validate(xsuaaToken.getAccessToken(), "", "key-id-1",
-				"https://myauth.com/jwks_uri");
+				"https://myauth.com/jwks_uri", null);
 		assertThat(validationResult.isErroneous(), is(true));
 		assertThat(validationResult.getErrorDescription(),
 				startsWith("Jwt token with signature algorithm '' can not be verified."));
@@ -168,7 +188,7 @@ public class JwtSignatureValidatorTest {
 				.retrieveTokenKeys(any())).thenThrow(OAuth2ServiceException.class);
 
 		ValidationResult result = cut.validate(xsuaaToken.getAccessToken(), "RS256", null,
-				"http://unavailable.com/token_keys");
+				"http://unavailable.com/token_keys", null);
 		assertThat(result.isErroneous(), is(true));
 		assertThat(result.getErrorDescription(),
 				containsString("Error retrieving Json Web Keys from Identity Service"));
@@ -183,7 +203,7 @@ public class JwtSignatureValidatorTest {
 	@Ignore
 	public void jsonECSignatureMatchesJWKS() {
 		String ecSignedToken = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.4iVk3-Y0v4RT4_9IaQlp-8dZ_4fsTzIylgrPTDLrEvTHBTyVS3tgPbr2_IZfLETtiKRqCg0aQ5sh9eIsTTwB1g";
-		assertThat(cut.validate(ecSignedToken, "ES256", "key-id-1", null).isValid(), is(true));
+		assertThat(cut.validate(ecSignedToken, "ES256", "key-id-1", null, null).isValid(), is(true));
 	}
 
 }
diff --git a/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/JwtValidatorBuilderTest.java b/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/JwtValidatorBuilderTest.java
index 1f51bc9d..1d9b3b33 100644
--- a/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/JwtValidatorBuilderTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/JwtValidatorBuilderTest.java
@@ -6,6 +6,7 @@ import com.sap.cloud.security.config.cf.CFConstants.XSUAA;
 import com.sap.cloud.security.token.Token;
 import com.sap.cloud.security.token.validation.ValidationResults;
 import com.sap.cloud.security.token.validation.Validator;
+import org.junit.Ignore;
 import org.junit.Test;
 
 import java.net.URI;
@@ -19,6 +20,15 @@ public class JwtValidatorBuilderTest {
 
 	public static final Token TOKEN = mock(Token.class);
 
+	@Test
+	public void sameServiceConfiguration_getSameInstance() throws URISyntaxException {
+		TokenValidator tokenValidatorMock = createTokenValidatorMock();
+		OAuth2ServiceConfiguration configuration = createMockConfiguration();
+		JwtValidatorBuilder builder_1 = JwtValidatorBuilder.getInstance(configuration);
+		JwtValidatorBuilder builder_2 = JwtValidatorBuilder.getInstance(configuration);
+		assertThat(builder_1).isSameAs(builder_2);
+	}
+
 	@Test
 	public void withAudienceValidator_overridesXsuaaJwtAudienceValidator() throws URISyntaxException {
 		TokenValidator tokenValidatorMock = createTokenValidatorMock();
diff --git a/java-security/src/test/resources/jsonWebTokenKeys.json b/java-security/src/test/resources/jsonWebTokenKeys.json
index 062dee8d..25f27211 100644
--- a/java-security/src/test/resources/jsonWebTokenKeys.json
+++ b/java-security/src/test/resources/jsonWebTokenKeys.json
@@ -6,7 +6,7 @@
       "use": "sig",
       "kid": "key-id-0",
       "alg": "RS256",
-      "value": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmNM3OXfZS0Uu8eYZXCgG\nWFgVWQX6MHnadYk3zHZ5PIPeDY3ApaSGpMEiVwn1cT6KUVHMLHcmz1gsNy74pCnm\nCa22W7J3BoZulzR0A37ayMVrRHh3nffgySk8u581l02bvbcpab3e3EotSN5LixGT\n1VWZvDbalXf6n5kq459NWL6ZzkEs20MrOEk5cLdnsigTQUHSKIQ5TpldyDkJMm2z\nwOlrB2R984+QdlDFmoVH7Yaujxr2g0Z96u7W9Imik6wTiFc8W7eUXfhPGn7reVLq\n1/5o2Nz0Jx0ejFHDwTGncs+k1RBS6DbpTOMr9tkJEc3ZsX3Ft4OtqCkRXI5hUma+\nHwIDAQAB\n-----END PUBLIC KEY-----",
+      "value": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm1QaZzMjtEfHdimrHP3/\n2Yr+1z685eiOUlwybRVG9i8wsgOUh+PUGuQL8hgulLZWXU5MbwBLTECAEMQbcRTN\nVTolkq4i67EP6JesHJIFADbK1Ni0KuMcPuiyOLvDKiDEMnYG1XP3X3WCNfsCVT9Y\noU+lWIrZr/ZsIvQri8jczr4RkynbTBsPaAOygPUlipqDrpadMO1momNCbea/o6GP\nn38LxEw609ItfgDGhL6f/yVid5pFzZQWb+9l6mCuJww0hnhO6gt6Rv98OWDty9G0\nfrWAPyEfuIW9B+mR/2vGhyU9IbbWpvFXiy9RVbbsM538TCjd5JF2dJvxy24addC4\noQIDAQAB\n-----END PUBLIC KEY-----",
       "n": "AJjTNzl32UtFLvHmGVwoBlhYFVkF-jB52nWJN8x2eTyD3g2NwKWkhqTBIlcJ9XE-ilFRzCx3Js9YLDcu-KQp5gmttluydwaGbpc0dAN-2sjFa0R4d5334MkpPLufNZdNm723KWm93txKLUjeS4sRk9VVmbw22pV3-p-ZKuOfTVi-mc5BLNtDKzhJOXC3Z7IoE0FB0iiEOU6ZXcg5CTJts8DpawdkffOPkHZQxZqFR-2Gro8a9oNGferu1vSJopOsE4hXPFu3lF34Txp-63lS6tf-aNjc9CcdHoxRw8Exp3LPpNUQUug26UzjK_bZCRHN2bF9xbeDragpEVyOYVJmvh8"
     },
     {
diff --git a/java-security/src/test/resources/xsuaaAccessTokenRSA256_signedWithVerificationKey.txt b/java-security/src/test/resources/xsuaaAccessTokenRSA256_signedWithVerificationKey.txt
index 5b70afa7..6cd6798c 100644
--- a/java-security/src/test/resources/xsuaaAccessTokenRSA256_signedWithVerificationKey.txt
+++ b/java-security/src/test/resources/xsuaaAccessTokenRSA256_signedWithVerificationKey.txt
@@ -1,1 +1,1 @@
-eyJhbGciOiJSUzI1NiJ9.e30=.Fix5uWgo17FnuwSmkpvLG19KatPjL6yjzPdqu7Ny2IG43B7gT_jcxFDDVTaqYkCWyKv4QfbvtZCtWT9dhkCyeW49Pg2lRRNotLn4mv8FprIPRP_rvY0ryNpYjGlQAcY7pFjezPYvE9saxQtV7iqTCUx1jZ6KvP4e020Q7egmZM_Rtu8lA6pofeeaVJ6awpKFAeY0Gk_U5BlKcBZlUE0sZK_UmrTkHFXQk2xv5lE_Illim24LeC8LeJgYp2tWTvkKFx7xEKwkHnlE-DWqOnO1tQ29HgWhU65pOsrSwgKDG80QoVA1qnsYT8NHtOkR9hZuSsA8nIS3uExnUFx8aHlSEQ==
\ No newline at end of file
+eyJqa3UiOiJodHRwOi8vbG9jYWxob3N0OjY1MTQ4L3Rva2VuX2tleXMiLCJhbGciOiJSUzI1NiJ9.eyJncmFudF90eXBlIjoidXNlcl90b2tlbiIsInNjb3BlIjpbImJ1bGxldGluYm9hcmQtQzUyOTU0MDAuRGlzcGxheSIsImJ1bGxldGluYm9hcmQtQzUyOTU0MDAuVXBkYXRlIl0sImNpZCI6InNiLWNsaWVudElkIXQwODE1In0=.CetA62rQSNRj93S9mqaHrKJyzONKeEKcEJ9O5wObRD_RHrRNHAIOtXUgm2Qy6IrrhvPNY8CdNVmosrXOOebJZLwGdNNREqSz0Xu1tl3_Z3fzekq4dTV0yccTCfAYHVvUNbCdkXBYr2UTYULSMMXHXI5yuEtQaaQTxWN7k67Tgajq4BXUOT-asp3X-zhmMpmoTqAj8ARNrtaXUOzEt9inPgkMMhMJM3HT47p6SHe3XL8v0yBEIAsAQlxPPCjak9BSpCUUA-c6ZOEOIZBb346zZhCsV5SN1xC5cMZiFjHP6P0LIzQBEVlPpOy0NfIWdyH-o8FUUcI3nEzFx0qxc_TvuQ==
\ No newline at end of file
diff --git a/java-security/src/test/resources/xsuaaUserAccessTokenRSA256.txt b/java-security/src/test/resources/xsuaaUserAccessTokenRSA256.txt
index f4e5ca5a..17afd966 100644
--- a/java-security/src/test/resources/xsuaaUserAccessTokenRSA256.txt
+++ b/java-security/src/test/resources/xsuaaUserAccessTokenRSA256.txt
@@ -1,1 +1,1 @@
-eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ6aWQiOiJlMmY3ZmJkYi0wMzI2LTQwZTYtOTQwZi1kZmRkYWQwNTdmZjMiLCJncmFudF90eXBlIjoidXJuOmlldGY6cGFyYW1zOm9hdXRoOmdyYW50LXR5cGU6c2FtbDItYmVhcmVyIiwidXNlcl9uYW1lIjoidGVzdFVzZXIiLCJvcmlnaW4iOiJ1c2VySWRwIiwiZXhwIjo2OTc0MDMxNjAwLCJpYXQiOjE1NzQ0MjYwNDMsImVtYWlsIjoidGVzdFVzZXJAdGVzdC5vcmciLCJjaWQiOiJzYi1qYXZhLWhlbGxvLXdvcmxkIn0.YFdsy1NMZMcRPnnPmIF9IggMyjmxkXGKf2gCFAD20siVbdkEC_eAtO-TZk_VgNpm0TOLbWEfPensnNc0dsE9QfGtOq15HshjrAmc4qv7JuWq6LoMz9lzEpKF2rh6Zn2fyFhZTzR7bS3qIMAFYHgBL7yTDBQprxMW_8_qtuGR7mvw5GZMBujAS2s7f3v7gSuluVr68oCsTPTqkThY-Y0kixoeINTnkiEXWj31i294oniMEZvKwMcWlvguLolAxmOflDLPZn_xb5bNul3Y46IeQBGkNdd2oEVadjACwLgayn9LhsTtRh8JBaq7ttkoG6UGW_qMtNU3b7Qlh2VBOSp3tg
\ No newline at end of file
+eyJqa3UiOiJodHRwOi8vYXV0aC5jb20vdG9rZW5fa2V5cyIsImtpZCI6ImtleS1pZC0wIiwiYWxnIjoiUlMyNTYifQ==.eyJncmFudF90eXBlIjoidXNlcl90b2tlbiIsInVzZXJfbmFtZSI6InRlc3RVc2VyIiwib3JpZ2luIjoidXNlcklkcCIsImlzcyI6Imh0dHA6Ly9hdXRoLmNvbSIsImNpZCI6ImNsaWVudElkIn0=.mPvid5ZBGbxrS6eI5-vHmL8hdAvXhsYKmV3c8N8YWaQwUE03co2h9p6aHYj_SL-25dmOlc2tL12QtwKlHxbWv_JjFBUc53T4lMp1XKUR3kQKMawTDTLD0jTtYrxzMcWY2FzU4TyZmeeEutyqkv7JSMnFAvTvUQI5DOFwmeJiynM81OsDBZgkOJ0lb12kt1ymIYe1R5mbDUTz886cAbUXAz6Tr8CUX552LrAVrVotAwIy0508eUYOH5nfNAllhKT0Rq-FVK-UlWZ_jwVtpxlAam-NXAT7J_Wr4Ip71JeGCU_IoWCq1AGxEc64mb0-ygoenuImGYZELPEOAWzXiQ_cUQ==
\ No newline at end of file
diff --git a/samples/java-security-usage/src/test/java/com/sap/cloud/security/samples/HelloJavaServletIntegrationTest.java b/samples/java-security-usage/src/test/java/com/sap/cloud/security/samples/HelloJavaServletIntegrationTest.java
index 8f002b2e..cf373723 100644
--- a/samples/java-security-usage/src/test/java/com/sap/cloud/security/samples/HelloJavaServletIntegrationTest.java
+++ b/samples/java-security-usage/src/test/java/com/sap/cloud/security/samples/HelloJavaServletIntegrationTest.java
@@ -1,11 +1,8 @@
 package com.sap.cloud.security.samples;
 
-import com.sap.cloud.security.config.Environments;
-import com.sap.cloud.security.config.cf.CFConstants;
 import com.sap.cloud.security.test.SecurityTestRule;
 import com.sap.cloud.security.token.SecurityContext;
 import com.sap.cloud.security.xsuaa.http.HttpHeaders;
-import org.apache.commons.io.IOUtils;
 import org.apache.http.HttpStatus;
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
@@ -13,42 +10,24 @@ import org.apache.http.impl.client.HttpClients;
 import org.junit.*;
 
 import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.Properties;
 
 import static com.sap.cloud.security.config.Service.XSUAA;
-import static com.sap.cloud.security.config.cf.CFConstants.*;
 import static com.sap.cloud.security.token.TokenClaims.XSUAA.GRANT_TYPE;
 import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.GRANT_TYPE_CLIENT_CREDENTIALS;
 import static org.assertj.core.api.Assertions.assertThat;
 
 public class HelloJavaServletIntegrationTest {
 
-	private static Properties oldProperties;
-
 	@ClassRule
 	public static SecurityTestRule rule = SecurityTestRule.getInstance(XSUAA)
 			.useApplicationServer()
 			.addApplicationServlet(HelloJavaServlet.class, HelloJavaServlet.ENDPOINT);
 
-	@BeforeClass
-	public static void prepareTest() throws Exception {
-		oldProperties = System.getProperties();
-		System.setProperty(VCAP_SERVICES, IOUtils.resourceToString("/vcap.json", StandardCharsets.UTF_8));
-		assertThat(Environments.getCurrent().getXsuaaConfiguration()).isNotNull();
-		rule.setClientId(Environments.getCurrent().getXsuaaConfiguration().getClientId());
-	}
-
 	@After
-	public void tearDown() throws Exception {
+	public void tearDown() {
 		SecurityContext.clearToken();
 	}
 
-	@AfterClass
-	public static void restoreProperties() {
-		System.setProperties(oldProperties);
-	}
-
 	@Test
 	public void requestWithoutAuthorizationHeader_unauthenticated() throws IOException {
 		HttpGet request = createGetRequest(null);
@@ -66,7 +45,7 @@ public class HelloJavaServletIntegrationTest {
 	}
 
 	@Test
-	public void request_withValidTokenWithoutScopes_unauthorized() throws IOException {
+	public void requestWithValidTokenWithoutScopes_unauthorized() throws IOException {
 		String bearerAccessToken = rule.getPreconfiguredJwtGenerator()
 				.withClaimValue(GRANT_TYPE, GRANT_TYPE_CLIENT_CREDENTIALS)
 				.createToken()
@@ -78,15 +57,14 @@ public class HelloJavaServletIntegrationTest {
 	}
 
 	@Test
-	public void request_withValidToken_ok() throws IOException {
-		String  getBearerAccessToken = rule.getPreconfiguredJwtGenerator()
+	public void requestWithValidToken_ok() throws IOException {
+		String jwt = rule.getPreconfiguredJwtGenerator()
 				.withClaimValue(GRANT_TYPE, GRANT_TYPE_CLIENT_CREDENTIALS)
 				.withScopes(getGlobalScope("Read"))
 				.createToken()
 				.getBearerAccessToken();
-		HttpGet request = createGetRequest(getBearerAccessToken);
+		HttpGet request = createGetRequest(jwt);
 		try (CloseableHttpResponse response = HttpClients.createDefault().execute(request)) {
-			String responseBody = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
 			assertThat(response.getStatusLine().getStatusCode()).isEqualTo(HttpStatus.SC_OK); // 200
 		}
 	}
@@ -100,7 +78,6 @@ public class HelloJavaServletIntegrationTest {
 	}
 
 	private String getGlobalScope(String scope) {
-		String appId = Environments.getCurrent().getXsuaaConfiguration().getProperty(CFConstants.XSUAA.APP_ID);
-		return appId + '.' + scope;
+		return SecurityTestRule.DEFAULT_APP_ID + '.' + scope;
 	}
 }
\ No newline at end of file
diff --git a/samples/java-security-usage/src/test/resources/vcap.json b/samples/java-security-usage/src/test/resources/vcap.json
deleted file mode 100644
index 9b142633..00000000
--- a/samples/java-security-usage/src/test/resources/vcap.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
-  "xsuaa": [
-    {
-      "binding_name": null,
-      "credentials": {
-        "clientid": "sb-clientId!t20",
-        "clientsecret": "secret",
-        "identityzone": "uaa",
-        "identityzoneid": "uaa",
-        "uaadomain": "localhost",
-        "url": "http://localhost",
-        "xsappname": "java-security-usage!t1785"
-      },
-      "name": "xsuaa",
-      "plan": "broker",
-      "tags": [
-        "xsuaa"
-      ]
-    }
-  ]
-}
\ No newline at end of file