Repository: SAP/cloud-security-xsuaa-integration
Pull request #216 (2020-01-21 17:59:45)

[java-security] support XSA projects


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 e92da3e1..dca13120 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
@@ -9,10 +9,9 @@ package com.sap.xsa.security.container;
 import java.security.Principal;
 
 /**
- * @deprecated with version 2.4.0 in favor of the new SAP Java Container
+ * @apiNote 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 3778ad68..97ba130f 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
@@ -12,10 +12,9 @@ import java.util.Map;
 /**
  * Represents a token exchange request.
  * 
- * @deprecated with version 2.4.0 in favor of the new SAP Java Container
+ * @apiNote 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 ecf21dd6..5b762c57 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
@@ -10,10 +10,9 @@ 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
+ * @apiNote deprecated with version 2.4.0 in favor of the new SAP Java Container
  *             library.
  */
-@Deprecated
 public interface XSUserInfo {
 
 	/**
@@ -27,29 +26,44 @@ public interface XSUserInfo {
 	 * - origin: alias to an identity provider
 	 * 
 	 * - subaccount id: identifier for the subaccount
-	 * 
+	 *
+	 * @deprecated Can be replaced with
+	 *             {@code token.getClaimAsString(TokenClaims.XSUAA.USER_NAME)} from
+	 *             the {@code com.sap.cloud.security.token} package or with
+	 *             {@code token.getPrincipal()}.
 	 * @return user name
 	 * @throws XSUserInfoException
 	 *             if attribute is not available in the authentication token
 	 */
+	@Deprecated
 	public String getLogonName() throws XSUserInfoException;
 
 	/**
 	 * Given name of the user.
-	 * 
+	 *
+	 * @deprecated Can be replaced with
+	 *             {@code token.getClaimAsString(TokenClaims.XSUAA.GIVEN_NAME)} from
+	 *             the {@code com.sap.cloud.security.token} package. Only if
+	 *             it is not an external attribute.
 	 * @return given name
 	 * @throws XSUserInfoException
 	 *             if attribute is not available in the authentication token
 	 */
+	@Deprecated
 	public String getGivenName() throws XSUserInfoException;
 
 	/**
 	 * Familiy name of the user.
-	 * 
+	 *
+	 * @deprecated Can be replaced with
+	 *             {@code token.getClaimAsString(TokenClaims.XSUAA.FAMILY_NAME)}
+	 *             from the {@code com.sap.cloud.security.token} package. Only if
+	 *             it is not an external attribute.
 	 * @return family name
 	 * @throws XSUserInfoException
 	 *             if attribute is not available in the authentication token
 	 */
+	@Deprecated
 	public String getFamilyName() throws XSUserInfoException;
 
 	/**
@@ -57,79 +71,118 @@ public interface XSUserInfo {
 	 * which the user is persisted. For example, users that are authenticated by the
 	 * UAA itself with a username/password combination have their origin set to the
 	 * value uaa.
-	 *
+	 * 
+	 * @deprecated Can be replaced with
+	 *             {@code token.getClaimAsString(TokenClaims.XSUAA.ORIGIN)} from the
+	 *             {@code com.sap.cloud.security.token} package.
 	 * @return user origin
 	 * @throws XSUserInfoException
 	 *             if attribute is not available in the authentication token
 	 */
+	@Deprecated
 	public String getOrigin() throws XSUserInfoException;
 
 	/**
-	 * Return identity zone
-	 *
+	 * Return identity zone which is the same like the subaccount id (tenant id).
+	 * 
+	 * @deprecated Can be replaced with
+	 *             {@code token.getClaimAsString(TokenClaims.XSUAA.SUBACCOUNT_ID)}
+	 *             from the {@code com.sap.cloud.security.token} package.
 	 * @return identity zone
 	 * @throws XSUserInfoException
 	 *             if attribute is not available in the authentication token
 	 */
+	@Deprecated
 	String getIdentityZone() throws XSUserInfoException;
 
 	/**
-	 * Return subaccount identifier
+	 * Return subaccount identifier which is the same like the identity zone (tenant id).
 	 * 
+	 * @deprecated Can be replaced with
+	 *             {@code token.getClaimAsString(TokenClaims.XSUAA.SUBACCOUNT_ID)}
+	 *             from the {@code com.sap.cloud.security.token} package.
 	 * @return subaccount identifier
 	 * @throws XSUserInfoException
 	 *             if attribute is not available in the authentication token
 	 */
+	@Deprecated
 	public String getSubaccountId() throws XSUserInfoException;
 
 	/**
-	 * Return the subdomain of this subaccount
-	 * 
-	 * @return subdomain
-	 * @throws XSUserInfoException
-	 *             if attribute is not available in the authentication token
+	 * @apiNote Still Supported via {@codeXSUserInfoAdapter}
+	 * 	 	from the {@code com.sap.cloud.security.adapter.xs} package.
 	 */
 	public String getSubdomain() throws XSUserInfoException;
 
 	/**
 	 * Return the client id of the authentication token
-	 * 
+	 *
+	 * @deprecated Can be replaced with
+	 *             {@code token.getClaimAsString(TokenClaims.XSUAA.CLIENT_ID)} from
+	 *             the {@code com.sap.cloud.security.token} package.
 	 * @return client id
 	 * @throws XSUserInfoException
 	 *             if attribute is not available in the authentication token
 	 */
+	@Deprecated
 	public String getClientId() throws XSUserInfoException;
 
+	/**
+	 * @deprecated Can be replaced with {@code token.getClaimAsString(attribute)}
+	 *             from the {@code com.sap.cloud.security.token} package.
+	 */
 	@Deprecated
 	public String getJsonValue(String attribute) throws XSUserInfoException;
 
 	/**
 	 * Return the email of the user
 	 * 
+	 * @deprecated Can be replaced with
+	 *             {@code token.getClaimAsString(TokenClaims.XSUAA.EMAIL)} from the
+	 *             {@code com.sap.cloud.security.token} package.
+	 * 
 	 * @return email
 	 * @throws XSUserInfoException
 	 *             if attribute is not available in the authentication token
 	 */
+	@Deprecated
 	public String getEmail() throws XSUserInfoException;
 
+	/**
+	 * @deprecated use {@link #getHdbToken()} instead.
+	 * @return the hana database token
+	 * @throws XSUserInfoException
+	 */
 	@Deprecated // use getHdbToken
 	public String getDBToken() throws XSUserInfoException;
 
+	/**
+	 * @apiNote Still Supported via {@codeXSUserInfoAdapter}
+	 * 	 from the {@code com.sap.cloud.security.adapter.xs} package.
+	 */
 	public String getHdbToken() throws XSUserInfoException;
 
 	/**
 	 * Return authentication token
-	 * 
+	 *
+	 * @deprecated Can be replaced with {@code token.getAccessToken()} from the
+	 *             {@code com.sap.cloud.security.token} package.
 	 * @return authentication token
 	 */
+	@Deprecated
 	public String getAppToken();
 
-	@Deprecated
+	/**
+	 * @apiNote Still Supported via {@codeXSUserInfoAdapter}
+	 * 	 from the {@code com.sap.cloud.security.adapter.xs} package.
+	 */
 	public String getToken(String namespace, String name) throws XSUserInfoException;
 
 	/**
-	 * Return user attributes
-	 * 
+	 * Return user attributes.
+	 *
+	 * @apiNote Still Supported via {@codeXSUserInfoAdapter}
+	 * 	 	from the {@code com.sap.cloud.security.adapter.xs} package.
 	 * @param attributeName
 	 *            name of attribute
 	 * @return attribute values array
@@ -139,44 +192,63 @@ public interface XSUserInfo {
 	public String[] getAttribute(String attributeName) throws XSUserInfoException;
 
 	/**
-	 * Check if the authentication token contains user attributes
-	 * 
+	 * Check if the authentication token contains user attributes.
+	 * @apiNote Still Supported via {@codeXSUserInfoAdapter}
+	 * 	 	from the {@code com.sap.cloud.security.adapter.xs} package.
+	 *
 	 * @return true if user attributes are available
 	 * @throws XSUserInfoException
 	 *             if attribute is not available in the authentication token
 	 */
 	public boolean hasAttributes() throws XSUserInfoException;
 
-	@Deprecated
+	/**
+	 * @apiNote Still Supported via {@codeXSUserInfoAdapter}
+	 * 	 	from the {@code com.sap.cloud.security.adapter.xs} package.
+	 *
+	 * @param attributeName the name of the system attribute
+	 * @return the system attribute
+	 * @throws XSUserInfoException
+	 */
 	public String[] getSystemAttribute(String attributeName) throws XSUserInfoException;
 
 	/**
 	 * Check if a scope is present in the authentication token
-	 * 
+	 *
 	 * @param scope
 	 *            name of fully qualified scope
+	 * @deprecated can be replaced with {@code xsuaaToken.hasScope(scope)} from the
+	 *             {@code com.sap.cloud.security.token} package.
 	 * @return true if scope is available
 	 * @throws XSUserInfoException
 	 *             if attribute is not available in the authentication token
 	 */
+	@Deprecated
 	public boolean checkScope(String scope) throws XSUserInfoException;
 
 	/**
-	 * Check if a local scope is available in the authentication token
+	 * Check if a "local" scope is available in the authentication
+	 * 		token according to the {@code ScopeConverter}.
 	 * 
 	 * @param scope
-	 *            name of local scope (ommitting the xsappid)
-	 * @return true if local scope (scope without xsappid) is available
+	 *            name of local scope (the {@code XsuaaScopeConverter} omits the xsappid)
+	 * @deprecated can be replaced with {@code xsuaaToken.hasLocalScope(scope)} from
+	 *             the {@code com.sap.cloud.security.token} package.
+	 * @return true if local scope is available
 	 * @throws XSUserInfoException
 	 *             if attribute is not available in the authentication token
 	 */
+	@Deprecated
 	public boolean checkLocalScope(String scope) throws XSUserInfoException;
 
 	/**
 	 * Return additional authentication attributes included by the OAuth client
 	 * component. Note: this is data controlled by the requester of a token. Might
 	 * be not trustworthy.
-	 * 
+	 *
+	 * @apiNote Still Supported via {@codeXSUserInfoAdapter}
+	 * 	 	from the {@code com.sap.cloud.security.adapter.xs} package.
+	 *
 	 * @param attributeName
 	 *            name of the authentication attribute
 	 * @return addition authentication attributes
@@ -187,7 +259,10 @@ public interface XSUserInfo {
 
 	/**
 	 * In case of xsuaa broker plan tokens, it contains the service instance id
-	 * 
+	 *
+	 * @apiNote Still Supported via {@codeXSUserInfoAdapter}
+	 * 	 	from the {@code com.sap.cloud.security.adapter.xs} package.
+	 *
 	 * @return service instance id
 	 * @throws XSUserInfoException
 	 *             if attribute is not available in the authentication token
@@ -196,24 +271,37 @@ public interface XSUserInfo {
 
 	/**
 	 * OAuth Grant Type used for this token
-	 * 
+	 *
+	 * @deprecated can be replaced with {@code token.getGrantType()} from the
+	 *             {@code com.sap.cloud.security.token} package. This will give you
+	 *             a {@code GrantType} enum entry on which you can call
+	 *             {@code toString} to obtain the grant type as string, e.g.
+	 *             "client_credentials".
 	 * @return grant type
 	 * @throws XSUserInfoException
 	 *             if attribute is not available in the authentication token
 	 */
+	@Deprecated
 	public String getGrantType() throws XSUserInfoException;
 
 	/**
 	 * Check if a token issued for another OAuth client has been forwarded to a
 	 * different client,
-	 * 
+	 *
+	 * @deprecated tokens issued for a foreign OAuth Client Id and/or Identity Zone is no longer supported here.
+	 *
 	 * @return true if token was forwarded
 	 * @throws XSUserInfoException
 	 *             if attribute is not available in the authentication token
 	 */
+	@Deprecated
 	public boolean isInForeignMode() throws XSUserInfoException;
 
-	@Deprecated // use requestToken
+	/**
+	 * @deprecated can be replaced with token flows from the token-client library.
+	 * @throws XSUserInfoException
+	 */
+	@Deprecated
 	public String requestTokenForClient(String clientId, String clientSecret, String uaaUrl) throws XSUserInfoException;
 
 	/**
@@ -221,6 +309,7 @@ public interface XSUserInfo {
 	 * 
 	 * @param tokenRequest
 	 *            request data
+	 * @deprecated can be replaced with token flows from the token-client library.
 	 * @return requested token
 	 * @throws XSUserInfoException
 	 *             if attribute is not available in the authentication token
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 da137738..ea633fcc 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
@@ -7,10 +7,9 @@
 package com.sap.xsa.security.container;
 
 /**
- * @deprecated with version 2.4.0 in favor of the new SAP Java Container
+ * @apiNote 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/pom.xml b/java-security/pom.xml
index c40e67bd..acac3501 100644
--- a/java-security/pom.xml
+++ b/java-security/pom.xml
@@ -36,6 +36,13 @@
 			<scope>provided</scope>
 		</dependency>
 
+		<!-- needed for XSUserInfoAdapter-->
+		<dependency>
+			<groupId>com.sap.cloud.security.xsuaa</groupId>
+			<artifactId>api</artifactId>
+			<scope>provided</scope>
+		</dependency>
+
 		<!-- for call to tokenKeyService -->
 		<dependency>
 			<groupId>com.sap.cloud.security.xsuaa</groupId>
@@ -76,6 +83,12 @@
 			<artifactId>assertj-core</artifactId>
 			<scope>test</scope>
 		</dependency>
+		<dependency>
+			<groupId>com.github.stefanbirkner</groupId>
+			<artifactId>system-rules</artifactId>
+			<version>1.19.0</version>
+			<scope>test</scope>
+		</dependency>
     </dependencies>
 
 	<build>
diff --git a/java-security/src/main/java/com/sap/cloud/security/adapter/spring/SAPOfflineTokenServicesCloud.java b/java-security/src/main/java/com/sap/cloud/security/adapter/spring/SAPOfflineTokenServicesCloud.java
index 1d35c75b..1553ae0f 100644
--- a/java-security/src/main/java/com/sap/cloud/security/adapter/spring/SAPOfflineTokenServicesCloud.java
+++ b/java-security/src/main/java/com/sap/cloud/security/adapter/spring/SAPOfflineTokenServicesCloud.java
@@ -2,11 +2,13 @@ package com.sap.cloud.security.adapter.spring;
 
 import com.sap.cloud.security.config.Environments;
 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.XsuaaToken;
 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.Assertions;
 import com.sap.cloud.security.xsuaa.client.OAuth2TokenKeyServiceWithCache;
 import com.sap.cloud.security.xsuaa.client.OidcConfigurationServiceWithCache;
 import com.sap.cloud.security.xsuaa.client.SpringOAuth2TokenKeyService;
@@ -25,12 +27,13 @@ import javax.annotation.Nonnull;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Set;
-import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
 /**
  * This constructor requires a dependency to Spring oauth and web.
- * <pre>{@code
+ * 
+ * <pre>
+ * {@code
  * <dependency>
  *     <groupId>org.springframework.security.oauth</groupId>
  *     <artifactId>spring-security-oauth2</artifactId>
@@ -41,13 +44,14 @@ import java.util.stream.Collectors;
  *     <artifactId>spring-beans</artifactId>
  *     <scope>provided</scope>
  * </dependency>
- * }</pre>
+ * }
+ * </pre>
  */
 public class SAPOfflineTokenServicesCloud implements ResourceServerTokenServices, InitializingBean {
 
-	private final Supplier<Validator<Token>> validatorSupplier;
 	private final OAuth2ServiceConfiguration serviceConfiguration;
 	private Validator<Token> tokenValidator;
+	private JwtValidatorBuilder jwtValidatorBuilder;
 
 	/**
 	 * Constructs an instance which can be used in the SAP CP Environment.
@@ -59,9 +63,11 @@ public class SAPOfflineTokenServicesCloud implements ResourceServerTokenServices
 	/**
 	 * Constructs an instance with custom configuration.
 	 *
-	 * @param serviceConfiguration the service configuration.
-	 *                             You can use {@link com.sap.cloud.security.config.Environments} in order to load
-	 *                             service configuration from the binding information in your environment.
+	 * @param serviceConfiguration
+	 *            the service configuration. You can use
+	 *            {@link com.sap.cloud.security.config.Environments} in order to
+	 *            load service configuration from the binding information in your
+	 *            environment.
 	 */
 	public SAPOfflineTokenServicesCloud(OAuth2ServiceConfiguration serviceConfiguration) {
 		this(serviceConfiguration, new RestTemplate());
@@ -70,27 +76,33 @@ public class SAPOfflineTokenServicesCloud implements ResourceServerTokenServices
 	/**
 	 * Constructs an instance with custom configuration and rest template.
 	 *
-	 * @param serviceConfiguration the service configuration.
-	 *                             You can use {@link com.sap.cloud.security.config.Environments} in order to load
-	 *                             service configuration from the binding information in your environment.
-	 * @param restOperations the spring rest template
+	 * @param serviceConfiguration
+	 *            the service configuration. You can use
+	 *            {@link com.sap.cloud.security.config.Environments} in order to
+	 *            load service configuration from the binding information in your
+	 *            environment.
+	 * @param restOperations
+	 *            the spring rest template
 	 */
-	public SAPOfflineTokenServicesCloud(OAuth2ServiceConfiguration serviceConfiguration, RestOperations restOperations) {
-		this(serviceConfiguration, () -> JwtValidatorBuilder.getInstance(serviceConfiguration)
+	public SAPOfflineTokenServicesCloud(OAuth2ServiceConfiguration serviceConfiguration,
+			RestOperations restOperations) {
+		this(serviceConfiguration, JwtValidatorBuilder.getInstance(serviceConfiguration)
 				.withOAuth2TokenKeyService(
 						OAuth2TokenKeyServiceWithCache.getInstance()
 								.withTokenKeyService(new SpringOAuth2TokenKeyService(restOperations)))
 				.withOidcConfigurationService(
 						OidcConfigurationServiceWithCache.getInstance()
-								.withOidcConfigurationService(new SpringOidcConfigurationService(restOperations))
-				)
-				.build());
+								.withOidcConfigurationService(new SpringOidcConfigurationService(restOperations))));
+
 	}
 
 	SAPOfflineTokenServicesCloud(OAuth2ServiceConfiguration serviceConfiguration,
-			Supplier<Validator<Token>> validatorSupplier) {
+			JwtValidatorBuilder jwtValidatorBuilder) {
+		Assertions.assertNotNull(serviceConfiguration, "serviceConfiguration is required.");
+		Assertions.assertNotNull(jwtValidatorBuilder, "jwtValidatorBuilder is required.");
+
 		this.serviceConfiguration = serviceConfiguration;
-		this.validatorSupplier = validatorSupplier;
+		this.jwtValidatorBuilder = jwtValidatorBuilder;
 	}
 
 	@Override
@@ -104,27 +116,28 @@ public class SAPOfflineTokenServicesCloud implements ResourceServerTokenServices
 			AuthorizationRequest authorizationRequest = new AuthorizationRequest(new HashMap<>(), null,
 					serviceConfiguration.getClientId(), scopes, new HashSet<>(), null,
 					true, "", "", null);
+			SecurityContext.setToken(token);
 			return new OAuth2Authentication(authorizationRequest.createOAuth2Request(), null);
 		} else {
 			throw new InvalidTokenException(validationResult.getErrorDescription());
 		}
 	}
 
-	private XsuaaToken checkAndCreateToken(@Nonnull String accessToken) {
-		try {
-			return new XsuaaToken(accessToken);
-		} catch (Exception e) {
-			throw new InvalidTokenException(e.getMessage());
-		}
-	}
-
 	@Override
 	public void afterPropertiesSet() {
-		tokenValidator = validatorSupplier.get();
+		tokenValidator = jwtValidatorBuilder.build();
 	}
 
 	@Override
 	public OAuth2AccessToken readAccessToken(String accessToken) {
 		throw new UnsupportedOperationException("Not supported: read access token");
 	}
+
+	private XsuaaToken checkAndCreateToken(@Nonnull String accessToken) {
+		try {
+			return new XsuaaToken(accessToken);
+		} catch (Exception e) {
+			throw new InvalidTokenException(e.getMessage());
+		}
+	}
 }
\ No newline at end of file
diff --git a/java-security/src/main/java/com/sap/cloud/security/adapter/spring/XSUserInfoSpringAdapter.java b/java-security/src/main/java/com/sap/cloud/security/adapter/spring/XSUserInfoSpringAdapter.java
new file mode 100644
index 00000000..dbc19798
--- /dev/null
+++ b/java-security/src/main/java/com/sap/cloud/security/adapter/spring/XSUserInfoSpringAdapter.java
@@ -0,0 +1,34 @@
+package com.sap.cloud.security.adapter.spring;
+
+import com.sap.cloud.security.adapter.xs.XSUserInfoAdapter;
+import com.sap.cloud.security.token.Token;
+import com.sap.cloud.security.token.XsuaaToken;
+import com.sap.xsa.security.container.XSUserInfoException;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
+
+@Deprecated // TODO is this class really required?
+public class XSUserInfoSpringAdapter extends XSUserInfoAdapter {
+
+	public XSUserInfoSpringAdapter(Token xsuaaToken) throws XSUserInfoException {
+		super(xsuaaToken);
+	}
+
+	public XSUserInfoSpringAdapter() throws XSUserInfoException {
+		super(readFromSpringSecurityContext());
+	}
+
+	private static Token readFromSpringSecurityContext() throws XSUserInfoException {
+		Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+		if (auth == null) {
+			throw new XSUserInfoException("User not authenticated (Spring Security Context is empty)");
+		}
+		if (!(auth.getDetails() instanceof OAuth2AuthenticationDetails)) {
+			throw new XSUserInfoException(
+					"User not authenticated (Cannot parse User Details from Spring Security Context).");
+		}
+		String tokenValue = ((OAuth2AuthenticationDetails) auth.getDetails()).getTokenValue();
+		return new XsuaaToken(tokenValue);
+	}
+}
diff --git a/java-security/src/main/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapter.java b/java-security/src/main/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapter.java
new file mode 100644
index 00000000..22d01d8e
--- /dev/null
+++ b/java-security/src/main/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapter.java
@@ -0,0 +1,278 @@
+package com.sap.cloud.security.adapter.xs;
+
+import com.sap.cloud.security.json.JsonObject;
+import com.sap.cloud.security.json.JsonParsingException;
+import com.sap.cloud.security.token.GrantType;
+import com.sap.cloud.security.token.Token;
+import com.sap.cloud.security.token.XsuaaToken;
+import com.sap.xsa.security.container.XSTokenRequest;
+import com.sap.xsa.security.container.XSUserInfo;
+import com.sap.xsa.security.container.XSUserInfoException;
+
+import javax.annotation.Nullable;
+import java.util.Optional;
+import java.util.function.Supplier;
+
+import static com.sap.cloud.security.token.TokenClaims.XSUAA.*;
+
+public class XSUserInfoAdapter implements XSUserInfo {
+
+	static final String EXTERNAL_CONTEXT = "ext_ctx";
+	static final String CLAIM_ADDITIONAL_AZ_ATTR = "az_attr";
+	static final String XS_USER_ATTRIBUTES = "xs.user.attributes";
+	static final String XS_SYSTEM_ATTRIBUTES = "xs.system.attributes";
+	static final String HDB_NAMEDUSER_SAML = "hdb.nameduser.saml";
+	static final String SERVICEINSTANCEID = "serviceinstanceid";
+	static final String ZDN = "zdn";
+	static final String SYSTEM = "SYSTEM";
+	static final String HDB = "HDB";
+	static final String EXTERNAL_ATTR = "ext_attr";
+	private final XsuaaToken xsuaaToken;
+
+	public XSUserInfoAdapter(Token xsuaaToken) throws XSUserInfoException {
+		if (!(xsuaaToken instanceof XsuaaToken)) {
+			throw new XSUserInfoException("token needs to be an instance of XsuaaToken.");
+		}
+		this.xsuaaToken = (XsuaaToken) xsuaaToken;
+	}
+
+	public XSUserInfoAdapter(XsuaaToken xsuaaToken) throws XSUserInfoException {
+		if (xsuaaToken == null) {
+			throw new XSUserInfoException("token must not be null.");
+		}
+		this.xsuaaToken = xsuaaToken;
+	}
+
+	@Override
+	public String getLogonName() throws XSUserInfoException {
+		checkNotGrantTypeClientCredentials("getLogonName");
+		return getClaimValue(USER_NAME);
+	}
+
+	@Override
+	public String getGivenName() throws XSUserInfoException {
+		checkNotGrantTypeClientCredentials("getGivenName");
+		String externalAttributeName = getExternalAttribute(GIVEN_NAME);
+		if (externalAttributeName == null) {
+			return getClaimValue(GIVEN_NAME);
+		} else {
+			return externalAttributeName;
+		}
+	}
+
+	@Override
+	public String getFamilyName() throws XSUserInfoException {
+		checkNotGrantTypeClientCredentials("getFamilyName");
+		String externalAttributeName = getExternalAttribute(FAMILY_NAME);
+		if (externalAttributeName == null) {
+			return getClaimValue(FAMILY_NAME);
+		} else {
+			return externalAttributeName;
+		}
+	}
+
+	@Override
+	public String getOrigin() throws XSUserInfoException {
+		checkNotGrantTypeClientCredentials("getOrigin");
+		return getClaimValue(ORIGIN);
+	}
+
+	@Override
+	public String getIdentityZone() throws XSUserInfoException {
+		return getClaimValue(SUBACCOUNT_ID);
+	}
+
+	@Override
+	public String getSubaccountId() throws XSUserInfoException {
+		return getIdentityZone();
+	}
+
+	@Override
+	public String getSubdomain() throws XSUserInfoException {
+		return Optional.ofNullable(getExternalAttribute(ZDN)).orElseThrow(createXSUserInfoException(ZDN));
+	}
+
+	@Override
+	public String getClientId() throws XSUserInfoException {
+		return getClaimValue(CLIENT_ID);
+	}
+
+	@Override
+	public String getJsonValue(String attribute) throws XSUserInfoException {
+		return getClaimValue(attribute);
+	}
+
+	@Override
+	public String getEmail() throws XSUserInfoException {
+		checkNotGrantTypeClientCredentials("getEmail");
+		return getClaimValue(EMAIL);
+	}
+
+	// TODO
+	@Override
+	public String getDBToken() throws XSUserInfoException {
+		return getHdbToken();
+	}
+
+	// TODO
+	@Override
+	public String getHdbToken() throws XSUserInfoException {
+		return getToken(SYSTEM, HDB);
+	}
+
+	@Override
+	public String getAppToken() {
+		return xsuaaToken.getAccessToken();
+	}
+
+	@Override
+	public String getToken(String namespace, String name) throws XSUserInfoException {
+		// TODO 22.01.20 c5295400: TODO becaues foreignMode = false this is always false
+		if (!(getGrantType().equals(GrantType.CLIENT_CREDENTIALS)) && hasAttributes() && isInForeignMode()) {
+			throw new XSUserInfoException("The SecurityContext has been initialized with an access token of a\n"
+					+ "foreign OAuth Client Id and/or Identity Zone. Furthermore, the\n"
+					+ "access token contains attributes. Due to the fact that we want to\n"
+					+ "restrict attribute access to the application that provided the \n"
+					+ "attributes, the getToken function does not return a valid token");
+		}
+		if (!namespace.equals(SYSTEM)) {
+			throw new XSUserInfoException("Invalid namespace " + namespace);
+		}
+		if (name.equals(HDB)) {
+			String token;
+			if (xsuaaToken.hasClaim(EXTERNAL_CONTEXT)) {
+				token = getAttributeFromClaimAsString(EXTERNAL_CONTEXT, HDB_NAMEDUSER_SAML);
+			} else {
+				token = xsuaaToken.getClaimAsString(HDB_NAMEDUSER_SAML);
+			}
+			if (token == null) {
+				token = xsuaaToken.getAccessToken();
+			}
+			return token;
+		} else if (name.equals("JobScheduler")) {
+			return xsuaaToken.getAccessToken();
+		} else {
+			throw new XSUserInfoException("Invalid name " + name + " for namespace " + namespace);
+		}
+	}
+
+	@Override
+	public String[] getAttribute(String attributeName) throws XSUserInfoException {
+		checkNotGrantTypeClientCredentials("getAttribute");
+		return getMultiValueAttributeFromExtObject(XS_USER_ATTRIBUTES, attributeName);
+	}
+
+	@Override
+	public boolean hasAttributes() throws XSUserInfoException {
+		checkNotGrantTypeClientCredentials("hasAttributes");
+		if (xsuaaToken.hasClaim(EXTERNAL_CONTEXT)) {
+			JsonObject extContext = getClaimAsJsonObject(EXTERNAL_CONTEXT);
+			return extContext.contains(XS_USER_ATTRIBUTES) && !extContext.getJsonObject(EXTERNAL_CONTEXT).isEmpty();
+		} else {
+			return !getClaimAsJsonObject(XS_USER_ATTRIBUTES).isEmpty();
+		}
+	}
+
+	@Override
+	public String[] getSystemAttribute(String attributeName) throws XSUserInfoException {
+		return getMultiValueAttributeFromExtObject(XS_SYSTEM_ATTRIBUTES, attributeName);
+	}
+
+	@Override
+	public boolean checkScope(String scope) throws XSUserInfoException {
+		return xsuaaToken.hasScope(scope);
+	}
+
+	@Override
+	public boolean checkLocalScope(String scope) throws XSUserInfoException {
+		try {
+			return xsuaaToken.hasLocalScope(scope);
+		} catch (IllegalArgumentException e) {
+			throw new XSUserInfoException(e.getMessage());
+		}
+	}
+
+	@Override
+	public String getAdditionalAuthAttribute(String attributeName) throws XSUserInfoException {
+		return Optional.ofNullable(getAttributeFromClaimAsString(CLAIM_ADDITIONAL_AZ_ATTR, attributeName))
+				.orElseThrow(createXSUserInfoException(attributeName));
+	}
+
+	@Override
+	public String getCloneServiceInstanceId() throws XSUserInfoException {
+		return Optional.ofNullable(getExternalAttribute(SERVICEINSTANCEID))
+				.orElseThrow(createXSUserInfoException(SERVICEINSTANCEID));
+	}
+
+	@Override
+	public String getGrantType() throws XSUserInfoException {
+		return Optional.ofNullable(xsuaaToken.getGrantType())
+				.map(GrantType::toString)
+				.orElseThrow(createXSUserInfoException(GRANT_TYPE));
+	}
+
+	@Override
+	public boolean isInForeignMode() throws XSUserInfoException {
+		return false; // This is not supported
+	}
+
+	@Override
+	public String requestTokenForClient(String clientId, String clientSecret, String uaaUrl)
+			throws XSUserInfoException {
+		throw new UnsupportedOperationException("Not implemented.");
+	}
+
+	@Override
+	public String requestToken(XSTokenRequest tokenRequest) throws XSUserInfoException {
+		throw new UnsupportedOperationException("Not implemented.");
+	}
+
+	private String[] getMultiValueAttributeFromExtObject(String claimName, String attributeName)
+			throws XSUserInfoException {
+		JsonObject claimAsJsonObject = getClaimAsJsonObject(claimName);
+		return Optional.ofNullable(claimAsJsonObject)
+				.map(jsonObject -> jsonObject.getAsList(attributeName, String.class))
+				.map(values -> values.toArray(new String[] {}))
+				.orElseThrow(createXSUserInfoException(attributeName));
+	}
+
+	private void checkNotGrantTypeClientCredentials(String methodName) throws XSUserInfoException {
+		if (GrantType.CLIENT_CREDENTIALS == xsuaaToken.getGrantType()) {
+			String message = String.format("Method '%s' is not supported for grant type '%s'", methodName,
+					GrantType.CLIENT_CREDENTIALS);
+			throw new XSUserInfoException(message + GrantType.CLIENT_CREDENTIALS);
+		}
+	}
+
+	@Nullable
+	private String getAttributeFromClaimAsString(String claimName, String attributeName) throws XSUserInfoException {
+		return Optional.ofNullable(getClaimAsJsonObject(claimName))
+				.map(claim -> claim.getAsString(attributeName)).orElse(null);
+	}
+
+	private String getExternalAttribute(String attributeName) throws XSUserInfoException {
+		return getAttributeFromClaimAsString(EXTERNAL_ATTR, attributeName);
+	}
+
+	private Supplier<XSUserInfoException> createXSUserInfoException(String attribute) {
+		return () -> new XSUserInfoException("Invalid user attribute " + attribute);
+	}
+
+	private String getClaimValue(String claimname) throws XSUserInfoException {
+		String value = xsuaaToken.getClaimAsString(claimname);
+		if (value == null) {
+			throw new XSUserInfoException("Invalid user attribute " + claimname);
+		}
+		return value;
+	}
+
+	@Nullable
+	private JsonObject getClaimAsJsonObject(String claimName) throws XSUserInfoException {
+		try {
+			return xsuaaToken.getClaimAsJsonObject(claimName);
+		} catch (JsonParsingException e) {
+			throw createXSUserInfoException(claimName).get();
+		}
+	}
+
+}
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 8b41199b..26dc24fc 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
@@ -60,4 +60,10 @@ public interface OAuth2ServiceConfiguration {
 	 */
 	Service getService();
 
+	/**
+	 * Returns true, in case of XSUAA service runs in legacy mode.
+	 *
+	 * @return true in case it runs in legacy mode.
+	 */
+	boolean isLegacyMode();
 }
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
index 193aaf3a..8a610b47 100644
--- 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
@@ -12,6 +12,7 @@ import static com.sap.cloud.security.config.cf.CFConstants.*;
  */
 public class OAuth2ServiceConfigurationBuilder {
 	private Service service;
+	private boolean runInLegacyMode;
 	private final Map<String, String> properties = new HashMap<>();
 
 	private OAuth2ServiceConfigurationBuilder() {
@@ -69,7 +70,7 @@ public class OAuth2ServiceConfigurationBuilder {
 	}
 
 	public OAuth2ServiceConfigurationBuilder withProperty(String propertyName, String propertyValue) {
-		properties.put(propertyName, propertyValue);
+		properties.put(propertyName, propertyValue); // replaces values, that were already set
 		return this;
 	}
 
@@ -78,6 +79,11 @@ public class OAuth2ServiceConfigurationBuilder {
 		return this;
 	}
 
+	public OAuth2ServiceConfigurationBuilder runInLegacyMode(boolean isLegacyMode) {
+		this.runInLegacyMode = isLegacyMode;
+		return this;
+	}
+
 	/**
 	 * Builds an OAuth configuration ({@link OAuth2ServiceConfiguration}) based on
 	 * the properties applied.
@@ -116,6 +122,11 @@ public class OAuth2ServiceConfigurationBuilder {
 			public Service getService() {
 				return service;
 			}
+
+			@Override
+			public boolean isLegacyMode() {
+				return runInLegacyMode;
+			}
 		};
 
 	}
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 2d81b728..42164123 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
@@ -6,6 +6,7 @@ package com.sap.cloud.security.config.cf;
  */
 public class CFConstants {
 	public static final String VCAP_SERVICES = "VCAP_SERVICES";
+	public static final String VCAP_APPLICATION = "VCAP_APPLICATION";
 	public static final String CREDENTIALS = "credentials";
 	public static final String SERVICE_PLAN = "plan";
 	public static final String URL = "url";
@@ -40,7 +41,7 @@ public class CFConstants {
 	 * Represents the service plans on CF marketplace.
 	 */
 	public enum Plan {
-		DEFAULT, BROKER, APPLICATION;
+		DEFAULT, BROKER, APPLICATION, SPACE;
 
 		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 acc372e0..1ab0e918 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
@@ -21,36 +21,47 @@ class CFEnvParser {
 	private CFEnvParser() {
 	}
 
+	public static OAuth2ServiceConfiguration extract(Service service, JsonObject vcapServiceJson) {
+		return extract(service, vcapServiceJson, false);
+	}
+
 	/**
 	 * Loads all configurations of all identity service instances.
 	 *
 	 * @return the list of all found configurations or empty list, in case there are
 	 *         not service bindings.
 	 */
-	static Map<Service, List<OAuth2ServiceConfiguration>> loadAll(String vcapJsonString) {
+	static Map<Service, List<OAuth2ServiceConfiguration>> loadAll(String vcapServicesJson, String vcapApplicationJson) {
 		Map<Service, List<OAuth2ServiceConfiguration>> serviceConfigurations = new HashMap<>(); // NOSONAR
 		List<OAuth2ServiceConfiguration> allServices;
-		for (Service s : Service.values()) {
-			allServices = extractAllServices(s,
-					new DefaultJsonObject(vcapJsonString));
-			serviceConfigurations.put(s, allServices);
+
+		for (Service service : Service.values()) {
+			allServices = extractAllServices(service,
+					new DefaultJsonObject(vcapServicesJson),
+					runInLegacyMode(vcapApplicationJson));
+			serviceConfigurations.put(service, allServices);
 		}
 		return serviceConfigurations;
 	}
 
-	static List<OAuth2ServiceConfiguration> extractAllServices(Service service, JsonObject vcapJsonObject) {
-		List<JsonObject> serviceJsonObjects = vcapJsonObject.getJsonObjects(service.getCFName());
+	private static boolean runInLegacyMode(String vcapApplicationJson) {
+		return new DefaultJsonObject(vcapApplicationJson).contains("xs_api");
+	}
+
+	static List<OAuth2ServiceConfiguration> extractAllServices(Service service, JsonObject vcapServicesJson,
+			boolean isLegacyMode) {
+		List<JsonObject> serviceJsonObjects = vcapServicesJson.getJsonObjects(service.getCFName());
 		if (service == XSUAA && serviceJsonObjects.size() > 1) {
 			logger.warn(
 					"More than one service configuration available for service {}. Please make use of unified 'broker' plan.",
 					service);
 		}
 		return serviceJsonObjects.stream()
-				.map((JsonObject serviceJsonObject) -> extract(service, serviceJsonObject))
+				.map((JsonObject serviceJsonObject) -> extract(service, serviceJsonObject, isLegacyMode))
 				.collect(Collectors.toList());
 	}
 
-	public static OAuth2ServiceConfiguration extract(Service service, JsonObject serviceJsonObject) {
+	static OAuth2ServiceConfiguration extract(Service service, JsonObject serviceJsonObject, boolean isLegacyMode) {
 		Map<String, String> serviceBindingProperties = serviceJsonObject.getKeyValueMap();
 		try {
 			Map<String, String> serviceBindingCredentials = serviceJsonObject.getJsonObject(CFConstants.CREDENTIALS)
@@ -59,6 +70,7 @@ class CFEnvParser {
 			return OAuth2ServiceConfigurationBuilder.forService(service)
 					.withProperties(serviceBindingCredentials)
 					.withProperty(SERVICE_PLAN, serviceBindingProperties.get(SERVICE_PLAN))
+					.runInLegacyMode(isLegacyMode)
 					.build();
 		} catch (JsonParsingException e) {
 			String errDescription = "The credentials of 'VCAP_SERVICES' can not be parsed for service '"
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 1bc87914..03fecf49 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,7 @@
 package com.sap.cloud.security.config.cf;
 
 import static com.sap.cloud.security.config.Service.XSUAA;
+import static com.sap.cloud.security.config.cf.CFConstants.VCAP_APPLICATION;
 import static com.sap.cloud.security.config.cf.CFConstants.VCAP_SERVICES;
 
 import javax.annotation.Nullable;
@@ -37,7 +38,8 @@ public class CFEnvironment implements Environment {
 		CFEnvironment instance = new CFEnvironment();
 		instance.systemEnvironmentProvider = systemEnvironmentProvider;
 		instance.systemPropertiesProvider = systemPropertiesProvider;
-		instance.serviceConfigurations = CFEnvParser.loadAll(instance.extractVcapJsonString());
+		instance.serviceConfigurations = CFEnvParser.loadAll(instance.extractVcapServicesJson(),
+				instance.extractVcapApplicationJson());
 		return instance;
 	}
 
@@ -79,9 +81,8 @@ public class CFEnvironment implements Environment {
 	 *             necessary with the unified broker plan, this method is
 	 *             deprecated.
 	 */
-	@Deprecated
 	List<OAuth2ServiceConfiguration> loadAllForService(Service service) {
-		return serviceConfigurations.getOrDefault(service, new ArrayList<>());
+		return serviceConfigurations.getOrDefault(service, Collections.emptyList());
 	}
 
 	@Override
@@ -89,7 +90,7 @@ public class CFEnvironment implements Environment {
 		return Type.CF;
 	}
 
-	private String extractVcapJsonString() {
+	private String extractVcapServicesJson() {
 		String env = systemPropertiesProvider.apply(VCAP_SERVICES);
 		if (env == null) {
 			env = systemEnvironmentProvider.apply(VCAP_SERVICES);
@@ -97,24 +98,31 @@ public class CFEnvironment implements Environment {
 		return env != null ? env : "{}";
 	}
 
+	private String extractVcapApplicationJson() {
+		String env = System.getenv(VCAP_APPLICATION);
+		return env != null ? env : "{}";
+	}
+
 	private OAuth2ServiceConfiguration loadXsuaa() {
 		Optional<OAuth2ServiceConfiguration> applicationService = Optional
 				.ofNullable(loadForServicePlan(XSUAA, Plan.APPLICATION));
 		Optional<OAuth2ServiceConfiguration> brokerService = Optional
 				.ofNullable(loadForServicePlan(XSUAA, Plan.BROKER));
+		Optional<OAuth2ServiceConfiguration> legacyService = Optional
+				.ofNullable(loadForServicePlan(XSUAA, Plan.SPACE));
 		if (applicationService.isPresent()) {
 			return applicationService.get();
 		}
-		return brokerService.orElse(null);
+		return brokerService.orElse(legacyService.orElse(null));
 	}
 
 	/**
 	 * Loads the configuration for a dedicated service plan.
 	 *
 	 * @param service
-	 * 		the name of the service
+	 *            the name of the service
 	 * @param plan
-	 * 		the name of the service plan
+	 *            the name of the service plan
 	 * @return the configuration or null, if there is not such binding information
 	 *         for the given service 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 202450bf..c2ca9409 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
@@ -34,6 +34,11 @@ public class DefaultJsonObject implements JsonObject {
 		return getJsonObject().has(key);
 	}
 
+	@Override
+	public boolean isEmpty() {
+		return getJsonObject().isEmpty();
+	}
+
 	@Override
 	public <T> List<T> getAsList(String name, Class<T> type) {
 		return getJSONArray(name).map(jsonArray -> castToListOfType(jsonArray, type)).orElse(Collections.emptyList());
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 6b54a44a..935316a4 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
@@ -17,6 +17,13 @@ public interface JsonObject {
 	 */
 	boolean contains(String name);
 
+	/**
+	 * Method to check if the underlying json object is empty.
+	 *
+	 * @return true if the jsonObject is empty.
+	 */
+	boolean isEmpty();
+
 	/**
 	 * Parses the json object for the given property {@code name} and returns a list
 	 * of type {@link T}. If the property with the given name is not found, an empty
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 69a4d6b1..b4ffc0d4 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
@@ -1,6 +1,7 @@
 package com.sap.cloud.security.token;
 
 import com.sap.cloud.security.json.DefaultJsonObject;
+import com.sap.cloud.security.json.JsonObject;
 import com.sap.cloud.security.xsuaa.Assertions;
 import com.sap.cloud.security.xsuaa.jwt.Base64JwtDecoder;
 import com.sap.cloud.security.xsuaa.jwt.DecodedJwt;
@@ -21,8 +22,8 @@ import static com.sap.cloud.security.token.TokenClaims.NOT_BEFORE;
  * token header parameters and claims.
  */
 public abstract class AbstractToken implements Token {
-	private final DefaultJsonObject headerJsonObject;
-	private final DefaultJsonObject payloadJsonObject;
+	private final DefaultJsonObject tokenHeader;
+	private final DefaultJsonObject tokenBody;
 	private final String accessToken;
 
 	public AbstractToken(@Nonnull DecodedJwt decodedJwt) {
@@ -42,43 +43,49 @@ public abstract class AbstractToken implements Token {
 	}
 
 	AbstractToken(String jsonHeader, String jsonPayload, String accessToken) {
-		headerJsonObject = new DefaultJsonObject(jsonHeader);
-		payloadJsonObject = new DefaultJsonObject(jsonPayload);
+		tokenHeader = new DefaultJsonObject(jsonHeader);
+		tokenBody = new DefaultJsonObject(jsonPayload);
 		this.accessToken = accessToken;
 	}
 
 	@Nullable
 	@Override
 	public String getHeaderParameterAsString(@Nonnull String headerName) {
-		return headerJsonObject.getAsString(headerName);
+		return tokenHeader.getAsString(headerName);
 	}
 
 	@Override
 	public boolean hasClaim(@Nonnull String claimName) {
-		return payloadJsonObject.contains(claimName);
+		return tokenBody.contains(claimName);
 	}
 
 	@Override
 	public boolean hasHeaderParameter(@Nonnull String parameterName) {
-		return headerJsonObject.contains(parameterName);
+		return tokenHeader.contains(parameterName);
 	}
 
 	@Nullable
 	@Override
 	public String getClaimAsString(@Nonnull String claimName) {
-		return payloadJsonObject.getAsString(claimName);
+		return tokenBody.getAsString(claimName);
 	}
 
 	@Nullable
 	@Override
 	public List<String> getClaimAsStringList(@Nonnull String claimName) {
-		return payloadJsonObject.getAsList(claimName, String.class);
+		return tokenBody.getAsList(claimName, String.class);
+	}
+
+	@Nullable
+	@Override
+	public JsonObject getClaimAsJsonObject(String claimName) {
+		return tokenBody.getJsonObject(claimName);
 	}
 
 	@Nullable
 	@Override
 	public Instant getExpiration() {
-		return payloadJsonObject.getAsInstant(EXPIRATION);
+		return tokenBody.getAsInstant(EXPIRATION);
 	}
 
 	@Override
@@ -90,7 +97,7 @@ public abstract class AbstractToken implements Token {
 	@Nullable
 	@Override
 	public Instant getNotBefore() {
-		return payloadJsonObject.getAsInstant(NOT_BEFORE);
+		return tokenBody.getAsInstant(NOT_BEFORE);
 	}
 
 	@Override
diff --git a/java-security/src/main/java/com/sap/cloud/security/token/GrantType.java b/java-security/src/main/java/com/sap/cloud/security/token/GrantType.java
index b18ae8e8..d57e7719 100644
--- a/java-security/src/main/java/com/sap/cloud/security/token/GrantType.java
+++ b/java-security/src/main/java/com/sap/cloud/security/token/GrantType.java
@@ -8,8 +8,9 @@ import javax.annotation.Nullable;
  */
 public enum GrantType {
 	CLIENT_CREDENTIALS("client_credentials"), USER_TOKEN("user_token"), REFRESH_TOKEN("refresh_token"), PASSWORD(
-			"password"), JWT_BEARER("urn:ietf:params:oauth:grant-type:jwt-bearer"), CLIENT_X509(
-					"client_x509"), AUTHORIZATION_CODE("authorization_code");
+			"password"), JWT_BEARER("urn:ietf:params:oauth:grant-type:jwt-bearer"), SAML2_BEARER(
+					"urn:ietf:params:oauth:grant-type:saml2-bearer"), CLIENT_X509(
+							"client_x509"), AUTHORIZATION_CODE("authorization_code");
 
 	private String claimName;
 
diff --git a/java-security/src/main/java/com/sap/cloud/security/token/SecurityContext.java b/java-security/src/main/java/com/sap/cloud/security/token/SecurityContext.java
index fdc0a2d7..02549687 100644
--- a/java-security/src/main/java/com/sap/cloud/security/token/SecurityContext.java
+++ b/java-security/src/main/java/com/sap/cloud/security/token/SecurityContext.java
@@ -1,11 +1,15 @@
 package com.sap.cloud.security.token;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import javax.annotation.Nullable;
 
 /**
  * Thread wide {@link Token} storage.
  */
 public class SecurityContext {
+	private static final Logger LOGGER = LoggerFactory.getLogger(SecurityContext.class);
 
 	private SecurityContext() {
 	}
@@ -19,6 +23,8 @@ public class SecurityContext {
 	 *            token to be saved.
 	 */
 	public static void setToken(Token token) {
+		LOGGER.info("Sets access token of service %s to SecurityContext (thread-locally).",
+				token != null ? token.getService() : "null");
 		tokenStorage.set(token);
 	}
 
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 68f44da8..212ff6f1 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
@@ -1,6 +1,7 @@
 package com.sap.cloud.security.token;
 
 import com.sap.cloud.security.config.Service;
+import com.sap.cloud.security.json.JsonObject;
 import com.sap.cloud.security.json.JsonParsingException;
 
 import javax.annotation.Nonnull;
@@ -50,7 +51,7 @@ public interface Token {
 	 * Extracts the value as string for the given claim. If the claim is not found,
 	 * it will return null. If the given claim is not a string, it will throw a
 	 * {@link JsonParsingException}.
-	 * 
+	 *
 	 * @param claimName
 	 *            the name of the claim as defined here {@link TokenClaims}.
 	 * @return the corresponding string value of the given claim or null.
@@ -65,13 +66,25 @@ 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 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 or an empty list.
 	 */
 	List<String> getClaimAsStringList(@Nonnull String claimName);
 
+	/**
+	 * Extracts the value of the given as a JsonObject. Use this to extract nested
+	 * objects. If the claim is not found, it will return null. If the vale for the
+	 * given claim is not an object, it will throw a {@link JsonParsingException}.
+	 *
+	 * @param claimName
+	 *            the name of the claim for which the object should be extracted.
+	 * @return the corresponding {@link JsonObject} for the given claim.
+	 */
+	@Nullable
+	JsonObject getClaimAsJsonObject(@Nonnull String claimName);
+
 	/**
 	 * Returns the moment in time when the token will be expired.
 	 *
@@ -137,5 +150,6 @@ public interface Token {
 	 *
 	 * @return the grant type
 	 **/
-	public GrantType getGrantType();
+	GrantType getGrantType();
+
 }
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 b45849e5..6e7cf064 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
@@ -2,6 +2,7 @@ package com.sap.cloud.security.token.validation;
 
 import com.sap.cloud.security.xsuaa.Assertions;
 
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -23,6 +24,11 @@ public class CombiningValidator<T> implements Validator<T> {
 		this.validators = validators;
 	}
 
+	public CombiningValidator(Validator<T>... validators) {
+		Assertions.assertNotNull(validators, "validators must not be null.");
+		this.validators = Arrays.asList(validators);
+	}
+
 	@Override
 	public ValidationResult validate(T t) {
 		for (Validator<T> validator : validators) {
@@ -32,8 +38,19 @@ public class CombiningValidator<T> implements Validator<T> {
 				return result;
 			}
 		}
-		validationListeners.forEach(ValidationListener::onValidationSuccess);
-		return ValidationResults.createValid();
+		return createValidationResult();
+	}
+
+	private ValidationResult createValidationResult() {
+		if (validators.isEmpty()) {
+			ValidationResult result = ValidationResults
+					.createInvalid("CombiningValidator must contain at least one validator!");
+			validationListeners.forEach((listener) -> listener.onValidationError(result));
+			return result;
+		} else {
+			validationListeners.forEach(ValidationListener::onValidationSuccess);
+			return ValidationResults.createValid();
+		}
 	}
 
 	public List<Validator<T>> getValidators() {
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 f732a338..cf685360 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
@@ -10,7 +10,6 @@ import static com.sap.cloud.security.xsuaa.jwk.JsonWebKeyConstants.*;
 import static java.nio.charset.StandardCharsets.*;
 
 import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
 
 import java.net.URI;
 import java.security.NoSuchAlgorithmException;
@@ -31,8 +30,6 @@ 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
@@ -47,7 +44,7 @@ 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);
+	private boolean runInLegacyMode;
 
 	public JwtSignatureValidator(OAuth2TokenKeyServiceWithCache tokenKeyService,
 			OidcConfigurationServiceWithCache oidcConfigurationService) {
@@ -63,9 +60,15 @@ public class JwtSignatureValidator implements Validator<Token> {
 		return this;
 	}
 
+	JwtSignatureValidator runInLegacyMode(boolean isLegacyMode) {
+		this.runInLegacyMode = isLegacyMode;
+		return this;
+	}
+
 	@Override
 	public ValidationResult validate(Token token) {
 		String jwksUri;
+		String keyId;
 
 		try {
 			jwksUri = getOrRequestJwksUri(token);
@@ -73,9 +76,10 @@ public class JwtSignatureValidator implements Validator<Token> {
 			if (configuration != null && configuration.hasProperty("verificationkey")) {
 				fallbackPublicKey = configuration.getProperty("verificationkey");
 			}
+			keyId = getOrDefaultKeyId(token);
 			return validate(token.getAccessToken(),
 					token.getHeaderParameterAsString(ALGORITHM_PARAMETER_NAME),
-					token.getHeaderParameterAsString(KEY_ID_PARAMETER_NAME),
+					keyId,
 					jwksUri,
 					fallbackPublicKey);
 		} catch (OAuth2ServiceException | IllegalArgumentException e) {
@@ -83,22 +87,26 @@ public class JwtSignatureValidator implements Validator<Token> {
 		}
 	}
 
-	// for testing
-	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), fallbackPublicKey);
+	@Nonnull
+	private String getOrDefaultKeyId(Token token) {
+		if (runInLegacyMode) {
+			return KEY_ID_VALUE_LEGACY;
+		}
+		if (token.hasHeaderParameter(KEY_ID_PARAMETER_NAME)) {
+			return token.getHeaderParameterAsString(KEY_ID_PARAMETER_NAME);
+		}
+		return DEFAULT_KEY_ID;
 	}
 
 	@Nonnull
 	private String getOrRequestJwksUri(Token token) throws OAuth2ServiceException, IllegalArgumentException {
+		if (runInLegacyMode) {
+			return configuration.getUrl() + "/token_keys";
+		}
 		if (token.hasHeaderParameter(KEYS_URL_PARAMETER_NAME)) {
 			return token.getHeaderParameterAsString(KEYS_URL_PARAMETER_NAME);
 		}
-		if (token.hasClaim(ISSUER)) {
+		if (token.hasClaim(ISSUER)) { // don't call in case of XSA AUth Code tokens
 			URI discoveryUri = DefaultOidcConfigurationService.getDiscoveryEndpointUri(token.getClaimAsString(ISSUER));
 			return oidcConfigurationService
 					.getOrRetrieveEndpoints(discoveryUri)
@@ -108,6 +116,16 @@ public class JwtSignatureValidator implements Validator<Token> {
 				"Token signature can not be validated as jwks uri can not be determined: Token does neither provide 'jku' header nor 'issuer' claim.");
 	}
 
+	// for testing
+	ValidationResult validate(String token, String tokenAlgorithm, 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), fallbackPublicKey);
+	}
+
 	private static class Validation {
 		JwtSignatureAlgorithm jwtSignatureAlgorithm;
 		PublicKey publicKey;
@@ -121,8 +139,7 @@ public class JwtSignatureValidator implements Validator<Token> {
 		}
 
 		public ValidationResult validate(OAuth2TokenKeyServiceWithCache tokenKeyService, String token,
-				String tokenAlgorithm,
-				@Nullable String tokenKeyId, URI tokenKeysUrl, String fallbackPublicKey) {
+				String tokenAlgorithm, String tokenKeyId, URI tokenKeysUrl, String fallbackPublicKey) {
 			ValidationResult validationResult;
 
 			validationResult = setJwtAlgorithm(tokenAlgorithm);
@@ -130,8 +147,7 @@ public class JwtSignatureValidator implements Validator<Token> {
 				return validationResult;
 			}
 
-			String keyId = tokenKeyId != null ? tokenKeyId : DEFAULT_KEY_ID;
-			validationResult = setPublicKey(tokenKeyService, keyId, tokenKeysUrl);
+			validationResult = setPublicKey(tokenKeyService, tokenKeyId, tokenKeysUrl);
 			if (validationResult.isErroneous()) {
 				if (fallbackPublicKey != null) {
 					try {
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 10f76dab..74f41374 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
@@ -164,7 +164,7 @@ public class JwtValidatorBuilder {
 		List<Validator<Token>> defaultValidators = new ArrayList<>();
 		defaultValidators.add(new JwtTimestampValidator());
 		JwtSignatureValidator signatureValidator = new JwtSignatureValidator(getTokenKeyServiceWithCache(),
-				getOidcConfigurationServiceWithCache());
+				getOidcConfigurationServiceWithCache()).runInLegacyMode(configuration.isLegacyMode());
 		signatureValidator.withOAuth2Configuration(configuration);
 		Optional.ofNullable(customAudienceValidator).ifPresent(defaultValidators::add);
 		defaultValidators.add(signatureValidator);
@@ -173,7 +173,9 @@ public class JwtValidatorBuilder {
 			if (customAudienceValidator == null) {
 				defaultValidators.add(createXsuaaAudienceValidator());
 			}
-			defaultValidators.add(new XsuaaJwtIssuerValidator(configuration.getProperty(UAA_DOMAIN)));
+			if (!configuration.isLegacyMode()) {
+				defaultValidators.add(new XsuaaJwtIssuerValidator(configuration.getProperty(UAA_DOMAIN)));
+			}
 
 		} else if (configuration.getService() == IAS) {
 			// TODO IAS
diff --git a/token-client/src/main/java/com/sap/cloud/security/xsuaa/jwk/JsonWebKeyConstants.java b/token-client/src/main/java/com/sap/cloud/security/xsuaa/jwk/JsonWebKeyConstants.java
index d79b84ca..2eb426f6 100644
--- a/token-client/src/main/java/com/sap/cloud/security/xsuaa/jwk/JsonWebKeyConstants.java
+++ b/token-client/src/main/java/com/sap/cloud/security/xsuaa/jwk/JsonWebKeyConstants.java
@@ -13,8 +13,11 @@ public class JsonWebKeyConstants {
 	public static final String KEY_TYPE_PARAMETER_NAME = "kty";
 	public static final String ALGORITHM_PARAMETER_NAME = "alg";
 	public static final String VALUE_PARAMETER_NAME = "value";
-	public static final String KEY_ID_PARAMETER_NAME = "kid";
 	public static final String KEYS_URL_PARAMETER_NAME = "jku";
+	public static final String KEY_ID_PARAMETER_NAME = "kid";
+
+	// Legacy Token Key ID
+	public static final String KEY_ID_VALUE_LEGACY = "legacy-token-key";
 
 	static final String BEGIN_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----";
 	static final String END_PUBLIC_KEY = "-----END PUBLIC KEY-----";


Test patch:
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 5e35d3f5..a501d39b 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
@@ -5,11 +5,14 @@ import static com.sap.cloud.security.token.TokenHeader.ALGORITHM;
 
 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.json.JsonParsingException;
 import com.sap.cloud.security.token.IasToken;
 import com.sap.cloud.security.token.Token;
 import com.sap.cloud.security.token.TokenClaims;
 import com.sap.cloud.security.token.XsuaaToken;
 import com.sap.cloud.security.xsuaa.jwt.JwtSignatureAlgorithm;
+import org.json.JSONException;
 import org.json.JSONObject;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -84,6 +87,25 @@ public class JwtGenerator {
 		return this;
 	}
 
+	/**
+	 * Sets the claim with the given name to the given string value.
+	 *
+	 * @param claimName
+	 *            the name of the claim to be set.
+	 * @param object
+	 *            the string value of the claim to be set.
+	 * @return the builder object.
+	 */
+	public JwtGenerator withClaimValue(String claimName, JsonObject object) {
+		try {
+			JSONObject wrappedObject = new JSONObject(object.toString());
+			jsonPayload.put(claimName, wrappedObject);
+		} catch (JSONException e) {
+			throw new JsonParsingException(e.getMessage());
+		}
+		return this;
+	}
+
 	/**
 	 * Sets the claim with the given name to the given string values.
 	 *
diff --git a/java-security/src/test/java/com/sap/cloud/security/adapter/spring/SAPOfflineTokenServicesCloudTest.java b/java-security/src/test/java/com/sap/cloud/security/adapter/spring/SAPOfflineTokenServicesCloudTest.java
index 3bf9447f..c399887f 100644
--- a/java-security/src/test/java/com/sap/cloud/security/adapter/spring/SAPOfflineTokenServicesCloudTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/adapter/spring/SAPOfflineTokenServicesCloudTest.java
@@ -5,9 +5,10 @@ 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.Token;
+import com.sap.cloud.security.token.SecurityContext;
+import com.sap.cloud.security.token.validation.CombiningValidator;
 import com.sap.cloud.security.token.validation.ValidationResults;
-import com.sap.cloud.security.token.validation.Validator;
+import com.sap.cloud.security.token.validation.validators.JwtValidatorBuilder;
 import org.apache.commons.io.IOUtils;
 import org.junit.Before;
 import org.junit.Test;
@@ -17,7 +18,6 @@ import org.springframework.security.oauth2.provider.OAuth2Authentication;
 
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
-import java.util.function.Supplier;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -27,24 +27,38 @@ public class SAPOfflineTokenServicesCloudTest {
 
 	private SAPOfflineTokenServicesCloud cut;
 	private String xsuaaToken;
+	private JwtValidatorBuilder jwtValidatorBuilderMock;
 
 	public SAPOfflineTokenServicesCloudTest() throws IOException {
-		xsuaaToken = IOUtils.resourceToString("/xsuaaScopesTokenRSA256.txt", StandardCharsets.UTF_8);
+		xsuaaToken = IOUtils.resourceToString("/xsuaaCCAccessTokenRSA256.txt", StandardCharsets.UTF_8);
 	}
 
 	@Before
 	public void setUp() {
-		cut = createSAPOfflineTokenServicesCloud((token) -> ValidationResults.createValid());
+		OAuth2ServiceConfiguration configuration = OAuth2ServiceConfigurationBuilder
+				.forService(Service.XSUAA)
+				.withProperty(CFConstants.XSUAA.APP_ID, "appId")
+				.withProperty(CFConstants.CLIENT_ID, "clientId")
+				.withProperty(CFConstants.XSUAA.UAA_DOMAIN, "localhost")
+				.build();
+
+		jwtValidatorBuilderMock = Mockito.spy(JwtValidatorBuilder.getInstance(configuration));
+		Mockito.when(jwtValidatorBuilderMock.build()).thenReturn(
+				new CombiningValidator<>(token -> ValidationResults.createValid()));
+
+		cut = new SAPOfflineTokenServicesCloud(configuration, jwtValidatorBuilderMock);
+		SecurityContext.clearToken();
 	}
 
 	@Test
-	public void loadAuthentication() throws IOException {
+	public void loadAuthentication() {
 		cut.afterPropertiesSet();
 		OAuth2Authentication authentication = cut.loadAuthentication(xsuaaToken);
 
 		assertThat(authentication.isAuthenticated()).isTrue();
 		assertThat(authentication.getOAuth2Request()).isNotNull();
-		assertThat(authentication.getOAuth2Request().getScope()).contains("openid");
+		assertThat(authentication.getOAuth2Request().getScope()).contains("ROLE_SERVICEBROKER", "uaa.resource");
+		assertThat(SecurityContext.getToken().getAccessToken()).isEqualTo(xsuaaToken);
 	}
 
 	@Test
@@ -64,47 +78,28 @@ public class SAPOfflineTokenServicesCloudTest {
 
 	@Test
 	public void loadAuthentication_tokenValidationFailed_throwsException() {
-		String errorDescription = "just not valid";
-		cut = createSAPOfflineTokenServicesCloud((token) -> ValidationResults.createInvalid(errorDescription));
+		Mockito.when(jwtValidatorBuilderMock.build()).thenCallRealMethod();
 		cut.afterPropertiesSet();
 
-		assertThatThrownBy(() -> cut.loadAuthentication(xsuaaToken)).isInstanceOf(InvalidTokenException.class)
-				.hasMessageContaining(errorDescription);
-
-	}
-
-	@Test
-	public void afterPropertiesSet() {
-		TestTokenValidatorSupplier mockValidator = Mockito.mock(TestTokenValidatorSupplier.class);
-		cut = new SAPOfflineTokenServicesCloud(Mockito.mock(OAuth2ServiceConfiguration.class), mockValidator);
+		assertThatThrownBy(() -> cut.loadAuthentication(xsuaaToken)).isInstanceOf(InvalidTokenException.class);
 
-		Mockito.verify(mockValidator, times(0)).get();
-		cut.afterPropertiesSet();
-		Mockito.verify(mockValidator, times(1)).get();
+		assertThat(SecurityContext.getToken()).isNull();
 	}
 
 	@Test
-	public void createInstancWithEmptyConfiguration_throwsException() {
+	public void createInstanceWithEmptyConfiguration_throwsException() {
 		cut = new SAPOfflineTokenServicesCloud(Mockito.mock(OAuth2ServiceConfiguration.class));
 		cut.afterPropertiesSet();
 		assertThatThrownBy(() -> cut.loadAuthentication(xsuaaToken)).isInstanceOf(InvalidTokenException.class);
 	}
 
-	private static class TestTokenValidatorSupplier implements Supplier<Validator<Token>> {
-		@Override
-		public Validator<Token> get() {
-			return null;
-		}
-	}
+	@Test
+	public void afterPropertiesSet() {
+		cut = new SAPOfflineTokenServicesCloud(Mockito.mock(OAuth2ServiceConfiguration.class), jwtValidatorBuilderMock);
 
-	private SAPOfflineTokenServicesCloud createSAPOfflineTokenServicesCloud(Validator<Token> tokenValidator) {
-		OAuth2ServiceConfiguration configuration = OAuth2ServiceConfigurationBuilder
-				.forService(Service.XSUAA)
-				.withProperty(CFConstants.XSUAA.APP_ID, "appId")
-				.withProperty(CFConstants.CLIENT_ID, "clientId")
-				.withProperty(CFConstants.XSUAA.UAA_DOMAIN, "localhost")
-				.build();
-		return new SAPOfflineTokenServicesCloud(configuration, () -> tokenValidator);
+		Mockito.verify(jwtValidatorBuilderMock, times(0)).build();
+		cut.afterPropertiesSet();
+		Mockito.verify(jwtValidatorBuilderMock, times(1)).build();
 	}
 
 }
\ No newline at end of file
diff --git a/java-security/src/test/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapterTest.java b/java-security/src/test/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapterTest.java
new file mode 100644
index 00000000..d8108cdb
--- /dev/null
+++ b/java-security/src/test/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapterTest.java
@@ -0,0 +1,306 @@
+package com.sap.cloud.security.adapter.xs;
+
+import com.sap.cloud.security.adapter.xs.XSUserInfoAdapter;
+import com.sap.cloud.security.json.JsonObject;
+import com.sap.cloud.security.token.GrantType;
+import com.sap.cloud.security.token.XsuaaScopeConverter;
+import com.sap.cloud.security.token.XsuaaToken;
+import com.sap.xsa.security.container.XSUserInfoException;
+import org.apache.commons.io.IOUtils;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.io.IOException;
+
+import static com.sap.cloud.security.adapter.xs.XSUserInfoAdapter.*;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.when;
+
+@Ignore
+public class XSUserInfoAdapterTest {
+
+	private static final String TEST_APP_ID = "testApp";
+	private final XsuaaToken token;
+	private XSUserInfoAdapter cut;
+	private XsuaaToken emptyToken;
+
+	public XSUserInfoAdapterTest() throws IOException {
+		emptyToken = new XsuaaToken(IOUtils.resourceToString("/xsuaaEmptyToken.txt", UTF_8));
+		token = new XsuaaToken(IOUtils.resourceToString("/xsuaaUserInfoAdapterToken.txt", UTF_8));
+	}
+
+	@Before
+	public void setUp() throws XSUserInfoException {
+		cut = new XSUserInfoAdapter(token.withScopeConverter(new XsuaaScopeConverter(TEST_APP_ID)));
+	}
+
+	// TODO 22.01.20 c5295400: implement external context fallback tests
+	@Test
+	public void testGetLogonName() throws XSUserInfoException {
+		assertThat(cut.getLogonName()).isEqualTo("TestUser");
+	}
+
+	@Test
+	public void testGetLogonName_grantTypeClientCredentials_throwsException() throws XSUserInfoException {
+		cut = new XSUserInfoAdapter(createMockToken(GrantType.CLIENT_CREDENTIALS));
+
+		assertThatThrownBy(() -> cut.getLogonName()).isInstanceOf(XSUserInfoException.class)
+				.hasMessageContaining("getLogonName")
+				.hasMessageContaining(GrantType.CLIENT_CREDENTIALS.toString());
+	}
+
+	@Test
+	public void testGetGivenName() throws XSUserInfoException {
+		assertThat(cut.getGivenName()).isEqualTo("TestUser");
+	}
+
+	@Test
+	public void testGetGivenName_grantTypeClientCredentials_throwsException() throws XSUserInfoException {
+		cut = new XSUserInfoAdapter(createMockToken(GrantType.CLIENT_CREDENTIALS));
+
+		assertThatThrownBy(() -> cut.getGivenName()).isInstanceOf(XSUserInfoException.class)
+				.hasMessageContaining("getGivenName")
+				.hasMessageContaining(GrantType.CLIENT_CREDENTIALS.toString());
+	}
+
+	@Test
+	public void testGetFamilyName() throws XSUserInfoException {
+		assertThat(cut.getFamilyName()).isEqualTo("unknown.org");
+	}
+
+	@Test
+	public void testGetFamilyName_grantTypeClientCredentials_throwsException() throws XSUserInfoException {
+		cut = new XSUserInfoAdapter(createMockToken(GrantType.CLIENT_CREDENTIALS));
+
+		assertThatThrownBy(() -> cut.getFamilyName()).isInstanceOf(XSUserInfoException.class)
+				.hasMessageContaining("getFamilyName")
+				.hasMessageContaining(GrantType.CLIENT_CREDENTIALS.toString());
+	}
+
+	@Test
+	public void testGetIdentityZone() throws XSUserInfoException {
+		assertThat(cut.getIdentityZone()).isEqualTo("paas");
+	}
+
+	@Test
+	public void testGetSubdomain() throws XSUserInfoException {
+		assertThat(cut.getSubdomain()).isEqualTo("paas");
+	}
+
+	@Test
+	public void testGetClientId() throws XSUserInfoException {
+		assertThat(cut.getClientId()).isEqualTo("sb-clone1!b5|LR-master!b5");
+	}
+
+	@Test
+	public void testGetJsonValue() throws XSUserInfoException {
+		assertThat(cut.getJsonValue("cid")).isEqualTo("sb-clone1!b5|LR-master!b5");
+	}
+
+	@Test
+	public void testGetEmail() throws XSUserInfoException {
+		assertThat(cut.getEmail()).isEqualTo("TestUser@uaa.org");
+	}
+
+	@Test
+	public void testGetEmail_grantTypeClientCredentials_throwsException() throws XSUserInfoException {
+		cut = new XSUserInfoAdapter(createMockToken(GrantType.CLIENT_CREDENTIALS));
+
+		assertThatThrownBy(() -> cut.getEmail()).isInstanceOf(XSUserInfoException.class)
+				.hasMessageContaining("getEmail")
+				.hasMessageContaining(GrantType.CLIENT_CREDENTIALS.toString());
+	}
+
+	@Test
+	public void testGetDBToken() throws XSUserInfoException {
+		assertThat(cut.getDBToken()).isEqualTo(cut.getAppToken());
+	}
+
+	@Test
+	public void testGetHdbToken() throws XSUserInfoException {
+		assertThat(cut.getHdbToken()).isEqualTo(cut.getAppToken());
+	}
+
+	@Test
+	public void testGetAppToken() throws IOException {
+		assertThat(cut.getAppToken()).isEqualTo(IOUtils.resourceToString("/xsuaaUserInfoAdapterToken.txt", UTF_8));
+	}
+
+	@Test
+	public void getToken_namespaceNotSystem_throwsException() {
+		assertThatThrownBy(() -> cut.getToken("any", "any")).isInstanceOf(XSUserInfoException.class);
+	}
+
+	@Test
+	public void getToken_fallbackToAccessToken() throws XSUserInfoException {
+		assertThat(cut.getToken(XSUserInfoAdapter.SYSTEM, XSUserInfoAdapter.HDB)).isEqualTo(token.getAccessToken());
+	}
+
+	@Test
+	public void getToken_fromExternalContext() throws XSUserInfoException {
+		String internalToken = "token";
+		JsonObject externalContextMock = Mockito.mock(JsonObject.class);
+		when(externalContextMock.getAsString(HDB_NAMEDUSER_SAML)).thenReturn(internalToken);
+		XsuaaToken mockToken = createMockToken(externalContextMock);
+
+		cut = new XSUserInfoAdapter(mockToken);
+
+		assertThat(cut.getToken(XSUserInfoAdapter.SYSTEM, XSUserInfoAdapter.HDB)).isEqualTo(internalToken);
+	}
+
+	@Test
+	public void getToken_fromHDBNamedUserSaml() throws XSUserInfoException {
+		String internalToken = "token";
+		XsuaaToken mockToken = createMockToken();
+		when(mockToken.getClaimAsString(HDB_NAMEDUSER_SAML)).thenReturn(internalToken);
+		when(mockToken.getClaimAsJsonObject(XS_USER_ATTRIBUTES)).thenReturn(Mockito.mock(JsonObject.class));
+
+		cut = new XSUserInfoAdapter(mockToken);
+
+		assertThat(cut.getToken(XSUserInfoAdapter.SYSTEM, XSUserInfoAdapter.HDB)).isEqualTo(internalToken);
+	}
+
+	@Test
+	public void testGetAttribute() throws XSUserInfoException {
+		String[] attribute = cut.getAttribute("usrAttr");
+		assertThat(attribute).contains("test");
+	}
+
+	@Test
+	public void testGetAttribute_grantTypeClientCredentials_throwsException() throws XSUserInfoException {
+		cut = new XSUserInfoAdapter(createMockToken(GrantType.CLIENT_CREDENTIALS));
+
+		assertThatThrownBy(() -> cut.getAttribute("any")).isInstanceOf(XSUserInfoException.class)
+				.hasMessageContaining("getAttribute")
+				.hasMessageContaining(GrantType.CLIENT_CREDENTIALS.toString());
+	}
+
+	@Test
+	public void testHasAttributes() throws XSUserInfoException {
+		assertThat(cut.hasAttributes()).isTrue();
+	}
+
+	@Test
+	public void testHasAttributes_grantTypeClientCredentials_throwsException() throws XSUserInfoException {
+		cut = new XSUserInfoAdapter(createMockToken(GrantType.CLIENT_CREDENTIALS));
+
+		assertThatThrownBy(() -> cut.hasAttributes()).isInstanceOf(XSUserInfoException.class)
+				.hasMessageContaining("hasAttributes")
+				.hasMessageContaining(GrantType.CLIENT_CREDENTIALS.toString());
+	}
+
+	@Test
+	public void testGetSystemAttribute() throws XSUserInfoException {
+		String[] systemAttributes = cut.getSystemAttribute("xs.saml.groups");
+		assertThat(systemAttributes).contains("g1");
+	}
+
+	@Test
+	public void testCheckScope() throws XSUserInfoException {
+		assertThat(cut.checkScope("testScope")).isTrue();
+	}
+
+	@Test
+	public void testCheckLocalScope() throws XSUserInfoException {
+		assertThat(cut.checkLocalScope("localScope")).isTrue();
+	}
+
+	@Test
+	public void testCheckLocalScope_appNameNull_throwsException() throws XSUserInfoException {
+		cut = new XSUserInfoAdapter(token.withScopeConverter(null));
+		assertThatThrownBy(() -> cut.checkLocalScope("localScope")).isInstanceOf(XSUserInfoException.class);
+	}
+
+	@Test
+	public void testGetAdditionalAuthAttribute() throws XSUserInfoException {
+		assertThat(cut.getAdditionalAuthAttribute("external_id")).isEqualTo("abcd1234");
+	}
+
+	@Test
+	public void testGetCloneServiceInstanceId() throws XSUserInfoException {
+		assertThat(cut.getCloneServiceInstanceId()).isEqualTo("brokerCloneServiceInstanceId");
+	}
+
+	@Test
+	public void testGetGrantType() throws XSUserInfoException {
+		assertThat(cut.getGrantType()).isEqualTo("urn:ietf:params:oauth:grant-type:saml2-bearer");
+	}
+
+	@Test
+	public void testIsInForeignMode() throws XSUserInfoException {
+		assertThat(cut.isInForeignMode()).isFalse();
+	}
+
+	@Test
+	public void testGetSubaccountId() throws XSUserInfoException {
+		assertThat(cut.getSubaccountId()).isEqualTo("paas");
+	}
+
+	@Test
+	public void testGetOrigin() throws XSUserInfoException {
+		assertThat(cut.getOrigin()).isEqualTo("useridp");
+	}
+
+	@Test
+	public void testGetOrigin_grantTypeClientCredentials_throwsException() throws XSUserInfoException {
+		cut = new XSUserInfoAdapter(createMockToken(GrantType.CLIENT_CREDENTIALS));
+
+		assertThatThrownBy(() -> cut.getOrigin()).isInstanceOf(XSUserInfoException.class)
+				.hasMessageContaining("getOrigin")
+				.hasMessageContaining(GrantType.CLIENT_CREDENTIALS.toString());
+	}
+
+	@Test
+	public void accessAttributes_doNotExist_throwsException() throws IOException, XSUserInfoException {
+		String nonExistingAttribute = "doesNotExist";
+		cut = new XSUserInfoAdapter(emptyToken.withScopeConverter(new XsuaaScopeConverter(TEST_APP_ID)));
+
+		assertThatThrownBy(() -> cut.getGrantType()).isInstanceOf(XSUserInfoException.class);
+		assertThatThrownBy(() -> cut.getLogonName()).isInstanceOf(XSUserInfoException.class);
+		assertThatThrownBy(() -> cut.getFamilyName()).isInstanceOf(XSUserInfoException.class);
+		assertThatThrownBy(() -> cut.getIdentityZone()).isInstanceOf(XSUserInfoException.class);
+		assertThatThrownBy(() -> cut.getDBToken()).isInstanceOf(XSUserInfoException.class);
+		assertThatThrownBy(() -> cut.getCloneServiceInstanceId()).isInstanceOf(XSUserInfoException.class);
+		assertThatThrownBy(() -> cut.getGrantType()).isInstanceOf(XSUserInfoException.class);
+
+		assertThatThrownBy(() -> cut.getJsonValue(nonExistingAttribute)).isInstanceOf(XSUserInfoException.class);
+		assertThatThrownBy(() -> cut.getAttribute(nonExistingAttribute)).isInstanceOf(XSUserInfoException.class);
+		assertThatThrownBy(() -> cut.getSystemAttribute(nonExistingAttribute)).isInstanceOf(XSUserInfoException.class);
+	}
+
+	@Test
+	public void testRequestTokenForClient_isNotImplemented() {
+		assertThatThrownBy(() -> cut.requestTokenForClient("", "", ""))
+				.isInstanceOf(UnsupportedOperationException.class)
+				.hasMessageContaining("Not implemented");
+	}
+
+	@Test
+	public void testRequestToken_isNotImplemented() {
+		assertThatThrownBy(() -> cut.requestToken(null))
+				.isInstanceOf(UnsupportedOperationException.class)
+				.hasMessageContaining("Not implemented");
+	}
+
+	private XsuaaToken createMockToken(GrantType grantType) {
+		XsuaaToken mockToken = Mockito.mock(XsuaaToken.class);
+		when(mockToken.getGrantType()).thenReturn(grantType);
+		return mockToken;
+	}
+
+	private XsuaaToken createMockToken() {
+		return createMockToken(GrantType.SAML2_BEARER);
+	}
+
+	private XsuaaToken createMockToken(JsonObject externalContext) {
+		final XsuaaToken mockToken = createMockToken();
+		when(mockToken.hasClaim(EXTERNAL_CONTEXT)).thenReturn(true);
+		when(mockToken.getClaimAsJsonObject(EXTERNAL_CONTEXT)).thenReturn(externalContext);
+		return mockToken;
+	}
+
+}
\ No newline at end of file
diff --git a/java-security/src/test/java/com/sap/cloud/security/config/cf/CFEnvParserTest.java b/java-security/src/test/java/com/sap/cloud/security/config/cf/CFEnvParserTest.java
index 6728dc56..992097fe 100644
--- a/java-security/src/test/java/com/sap/cloud/security/config/cf/CFEnvParserTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/config/cf/CFEnvParserTest.java
@@ -29,7 +29,7 @@ public class CFEnvParserTest {
 
 	@Test
 	public void getClientId() {
-		assertThat(cut.getClientId()).isEqualTo("xs2.usertoken");
+		assertThat(cut.getClientId()).isEqualTo("clientId");
 	}
 
 	@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 3c1ae786..395703ae 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
@@ -9,7 +9,9 @@ import com.sap.cloud.security.json.JsonObject;
 import com.sap.cloud.security.json.JsonParsingException;
 import org.apache.commons.io.IOUtils;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.contrib.java.lang.system.EnvironmentVariables;
 
 import java.io.IOException;
 import java.util.Map;
@@ -22,15 +24,20 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 public class CFEnvironmentTest {
 
+	@Rule
+	public final EnvironmentVariables environmentVariables = new EnvironmentVariables();
+
 	private String vcapXsuaa;
 	private String vcapMultipleXsuaa;
 	private String vcapIas;
+	private String vcapXsa;
 	private CFEnvironment cut;
 
 	public CFEnvironmentTest() throws IOException {
 		vcapXsuaa = IOUtils.resourceToString("/vcapXsuaaServiceSingleBinding.json", UTF_8);
 		vcapMultipleXsuaa = IOUtils.resourceToString("/vcapXsuaaServiceMultipleBindings.json", UTF_8);
 		vcapIas = IOUtils.resourceToString("/vcapIasServiceSingleBinding.json", UTF_8);
+		vcapXsa = IOUtils.resourceToString("/vcapXsuaaXsaSingleBinding.json", UTF_8);
 	}
 
 	@Before
@@ -79,6 +86,7 @@ public class CFEnvironmentTest {
 		// assertThat(cut.getIasServiceConfiguration().getDomain()).isEqualTo("auth.com");
 		assertThat(cut.getIasConfiguration().getUrl().toString())
 				.isEqualTo("https://application.acc.ondemand.com");
+		assertThat(cut.getIasConfiguration().isLegacyMode()).isFalse();
 
 		assertThat(cut.getXsuaaConfiguration()).isNull();
 		assertThat(cut.getXsuaaConfigurationForTokenExchange()).isNull();
@@ -88,10 +96,11 @@ public class CFEnvironmentTest {
 	public void getConfigurationOfOneXsuaaInstance() {
 		assertThat(cut.getXsuaaConfiguration()).isSameAs(cut.getXsuaaConfiguration());
 		assertThat(cut.getXsuaaConfiguration().getService()).isEqualTo(Service.XSUAA);
-		assertThat(cut.getXsuaaConfiguration().getClientId()).isEqualTo("xs2.usertoken");
+		assertThat(cut.getXsuaaConfiguration().getClientId()).isEqualTo("clientId");
 		assertThat(cut.getXsuaaConfiguration().getClientSecret()).isEqualTo("secret");
 		assertThat(cut.getXsuaaConfiguration().getProperty(XSUAA.UAA_DOMAIN)).isEqualTo("auth.com");
 		assertThat(cut.getXsuaaConfiguration().getUrl().toString()).isEqualTo("https://paastenant.auth.com");
+		assertThat(cut.getXsuaaConfiguration().isLegacyMode()).isFalse();
 
 		assertThat(cut.getNumberOfXsuaaConfigurations()).isEqualTo(1);
 		assertThat(cut.getXsuaaConfigurationForTokenExchange()).isSameAs(cut.getXsuaaConfiguration());
@@ -99,6 +108,25 @@ public class CFEnvironmentTest {
 		// assertThat(cut.getIasConfiguration()).isNull(); // TODO IAS
 	}
 
+	@Test
+	public void getConfigurationOfXsuaaInstanceInXsaSystem() {
+		environmentVariables.set(VCAP_SERVICES, vcapXsa);
+		environmentVariables.set(VCAP_APPLICATION, "{\"xs_api\": \"anyvalue\"}");
+		assertThat(System.getenv(VCAP_APPLICATION)).isNotNull();
+		cut = CFEnvironment.getInstance();
+
+		assertThat(cut.getXsuaaConfiguration().getService()).isEqualTo(Service.XSUAA);
+		assertThat(Plan.from(cut.getXsuaaConfiguration().getProperty(SERVICE_PLAN))).isEqualTo(Plan.SPACE);
+		assertThat(cut.getXsuaaConfiguration().getClientId()).isEqualTo("sb-java-hello-world!i1");
+		assertThat(cut.getXsuaaConfiguration().getClientSecret()).startsWith("fxnWLHqLh6KC0Wp/bbv8Gwbu50OEbpS");
+		assertThat(cut.getXsuaaConfiguration().getUrl().toString())
+				.isEqualTo("https://xsa-test.c.eu-de-2.cloud.sap:30132/uaa-security");
+		assertThat(cut.getXsuaaConfiguration().isLegacyMode()).isTrue();
+
+		assertThat(cut.getNumberOfXsuaaConfigurations()).isEqualTo(1);
+		assertThat(cut.getXsuaaConfigurationForTokenExchange()).isSameAs(cut.getXsuaaConfiguration());
+	}
+
 	@Test
 	public void getConfigurationOfMultipleInstance() {
 		cut = CFEnvironment.getInstance((str) -> vcapMultipleXsuaa, (str) -> null);
diff --git a/java-security/src/test/java/com/sap/cloud/security/json/DefaultJsonObjectTest.java b/java-security/src/test/java/com/sap/cloud/security/json/DefaultJsonObjectTest.java
index aac5652f..27a3fce9 100644
--- a/java-security/src/test/java/com/sap/cloud/security/json/DefaultJsonObjectTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/json/DefaultJsonObjectTest.java
@@ -41,6 +41,12 @@ public class DefaultJsonObjectTest {
 		assertThat(cut.contains("doesNotExist")).isFalse();
 	}
 
+	@Test
+	public void isEmpty() {
+		assertThat(cut.isEmpty()).isFalse();
+		assertThat(new DefaultJsonObject("{}").isEmpty()).isTrue();
+	}
+
 	@Test
 	public void getAsString_keyExists_returnsStringValue() {
 		assertThat(cut.getAsString(KEY_1)).isEqualTo(STRING_TEXT);
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 cb021091..370da904 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
@@ -1,5 +1,7 @@
 package com.sap.cloud.security.token;
 
+import com.sap.cloud.security.json.JsonObject;
+import com.sap.cloud.security.json.JsonParsingException;
 import org.apache.commons.io.IOUtils;
 import org.junit.Test;
 
@@ -9,6 +11,7 @@ import java.time.Instant;
 import java.util.Collections;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.mockito.Mockito.when;
 
 public class AbstractTokenTest {
@@ -89,4 +92,20 @@ public class AbstractTokenTest {
 		assertThat(cut.getBearerAccessToken()).isEqualTo("Bearer " + cut.getAccessToken());
 	}
 
+	@Test
+	public void getJsonObject() {
+		JsonObject externalAttributes = cut.getClaimAsJsonObject("ext_attr");
+		assertThat(externalAttributes.getAsString("enhancer")).isEqualTo("XSUAA");
+	}
+
+	@Test
+	public void getJsonObject_claimsIsNotAnObject_throwsException() {
+		assertThatThrownBy(() -> cut.getClaimAsJsonObject("client_id")).isInstanceOf(JsonParsingException.class);
+	}
+
+	@Test
+	public void getJsonObject_claimsDoesNotExist_isNull() {
+		assertThat(cut.getClaimAsJsonObject("doesNotExist")).isNull();
+	}
+
 }
\ No newline at end of file
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 33416a91..397459a6 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,7 +1,6 @@
 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;
 
@@ -15,7 +14,6 @@ public class XsuaaTokenTest {
 
 	private static final String APP_ID = "app1";
 
-	private final XsuaaToken scopesToken;
 	private XsuaaToken clientCredentialsToken;
 	private XsuaaToken userToken;
 
@@ -23,8 +21,6 @@ public class XsuaaTokenTest {
 		clientCredentialsToken = new XsuaaToken(
 				IOUtils.resourceToString("/xsuaaCCAccessTokenRSA256.txt", UTF_8));
 		userToken = new XsuaaToken(IOUtils.resourceToString("/xsuaaUserAccessTokenRSA256.txt", UTF_8));
-
-		scopesToken = new XsuaaToken(IOUtils.resourceToString("/xsuaaScopesTokenRSA256.txt", UTF_8));
 	}
 
 	@Test
@@ -57,11 +53,10 @@ 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.hasScope("openid")).isTrue();
-		assertThat(scopesToken.hasLocalScope("openid")).isTrue();
+		clientCredentialsToken.withScopeConverter(new XsuaaScopeConverter("uaa"));
+		assertThat(clientCredentialsToken.hasScope("uaa.resource")).isTrue();
+		assertThat(clientCredentialsToken.hasLocalScope("resource")).isTrue();
+		assertThat(clientCredentialsToken.hasLocalScope("ROLE_SERVICEBROKER")).isTrue();
 	}
 
 	@Test
diff --git a/java-security/src/test/java/com/sap/cloud/security/token/validation/CombiningValidatorTest.java b/java-security/src/test/java/com/sap/cloud/security/token/validation/CombiningValidatorTest.java
index 0d12ecf6..8437cc52 100644
--- a/java-security/src/test/java/com/sap/cloud/security/token/validation/CombiningValidatorTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/token/validation/CombiningValidatorTest.java
@@ -7,8 +7,6 @@ import org.mockito.Mockito;
 import java.util.ArrayList;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.assertj.core.util.Lists.newArrayList;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.times;
 
@@ -19,26 +17,18 @@ public class CombiningValidatorTest {
 	private static final String SECOND_ERROR_MESSAGE = "secondMessage";
 
 	@Test
-	public void construct_emptyToken_() {
-		assertThatThrownBy(() -> {
-			new CombiningValidator<>(null);
-		}).isInstanceOf(IllegalArgumentException.class).hasMessageContaining("validators must not be null");
-
-	}
-
-	@Test
-	public void validate_containsNoValidators_validResult() {
+	public void validate_containsNoValidators_invalidResult() {
 		Validator<Token> combiningValidator = new CombiningValidator<>(new ArrayList<>());
 
 		ValidationResult validationResult = combiningValidator.validate(TOKEN);
 
-		assertThat(validationResult.isValid()).isTrue();
+		assertThat(validationResult.isValid()).isFalse();
 	}
 
 	@Test
 	public void validate_twoValidValidators_validResult() {
 		Validator<Token> combiningValidator = new CombiningValidator<>(
-				newArrayList(TokenTestValidator.createValid(), TokenTestValidator.createValid()));
+				TokenTestValidator.createValid(), TokenTestValidator.createValid());
 
 		ValidationResult validationResult = combiningValidator.validate(TOKEN);
 
@@ -48,7 +38,7 @@ public class CombiningValidatorTest {
 	@Test
 	public void validate_twoInvalidValidators_invalidResult() {
 		Validator<Token> combiningValidator = new CombiningValidator<>(
-				newArrayList(TokenTestValidator.createInvalid(), TokenTestValidator.createInvalid()));
+				TokenTestValidator.createInvalid(), TokenTestValidator.createInvalid());
 
 		ValidationResult validationResult = combiningValidator.validate(TOKEN);
 
@@ -58,8 +48,8 @@ public class CombiningValidatorTest {
 	@Test
 	public void validate_twoInvalidValidators_containsOnlyOneErrorMessages() {
 		Validator<Token> combiningValidator = new CombiningValidator<>(
-				newArrayList(TokenTestValidator.createValid(), TokenTestValidator.createInvalid(FIRST_ERROR_MESSAGE),
-						TokenTestValidator.createInvalid(SECOND_ERROR_MESSAGE)));
+				TokenTestValidator.createValid(), TokenTestValidator.createInvalid(FIRST_ERROR_MESSAGE),
+				TokenTestValidator.createInvalid(SECOND_ERROR_MESSAGE));
 
 		String error = combiningValidator.validate(TOKEN).getErrorDescription();
 
@@ -68,8 +58,8 @@ public class CombiningValidatorTest {
 
 	@Test
 	public void registerValidationListener_validValidator_callsOnValidationSuccess() {
-		CombiningValidator<Token> combiningValidator = new CombiningValidator<>(newArrayList(
-				TokenTestValidator.createValid()));
+		CombiningValidator<Token> combiningValidator = new CombiningValidator<>(
+				TokenTestValidator.createValid());
 		ValidationListener validationListenerMock = Mockito.mock(ValidationListener.class);
 		combiningValidator.registerValidationListener(validationListenerMock);
 
@@ -81,8 +71,8 @@ public class CombiningValidatorTest {
 
 	@Test
 	public void registerValidationListener_invalidValidator_callsOnValidationSuccess() {
-		CombiningValidator<Token> combiningValidator = new CombiningValidator<>(newArrayList(
-				TokenTestValidator.createInvalid()));
+		CombiningValidator<Token> combiningValidator = new CombiningValidator<>(
+				TokenTestValidator.createInvalid());
 		ValidationListener validationListenerMock = Mockito.mock(ValidationListener.class);
 		combiningValidator.registerValidationListener(validationListenerMock);
 
@@ -94,8 +84,8 @@ public class CombiningValidatorTest {
 
 	@Test
 	public void registerValidationListener_listenerIsRemoved_isNotCalled() {
-		CombiningValidator<Token> combiningValidator = new CombiningValidator<>(newArrayList(
-				TokenTestValidator.createValid()));
+		CombiningValidator<Token> combiningValidator = new CombiningValidator<>(
+				TokenTestValidator.createValid());
 		ValidationListener validationListenerMock = Mockito.mock(ValidationListener.class);
 		combiningValidator.registerValidationListener(validationListenerMock);
 		combiningValidator.removeValidationListener(validationListenerMock);
@@ -108,9 +98,17 @@ public class CombiningValidatorTest {
 	@Test
 	public void toString_containsValidatorName() {
 		CombiningValidator<Token> combiningValidator = new CombiningValidator<>(
-				newArrayList(TokenTestValidator.createValid()));
+				TokenTestValidator.createValid());
 
 		assertThat(combiningValidator.toString()).contains(TokenTestValidator.class.getSimpleName());
 	}
 
+	@Test
+	public void getValidators() {
+		TokenTestValidator validator1 = TokenTestValidator.createValid();
+		TokenTestValidator validator2 = TokenTestValidator.createInvalid();
+		CombiningValidator<Token> cut = new CombiningValidator<Token>(validator1, validator2);
+
+		assertThat(cut.getValidators()).containsExactly(validator1, validator2);
+	}
 }
\ No newline at end of file
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 d7820fe4..3d7c7060 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
@@ -1,14 +1,28 @@
 package com.sap.cloud.security.token.validation;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.when;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
 
 import java.io.IOException;
-import java.net.URI;
 import java.net.URISyntaxException;
 import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.GregorianCalendar;
+import java.util.Map;
 
+import com.sap.cloud.security.config.OAuth2ServiceConfigurationBuilder;
+import com.sap.cloud.security.json.DefaultJsonObject;
+import com.sap.cloud.security.json.JsonObject;
+import com.sap.cloud.security.token.TokenClaims;
+import com.sap.cloud.security.util.HttpClientTestFactory;
 import org.apache.commons.io.IOUtils;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mockito;
 
@@ -21,21 +35,65 @@ import com.sap.cloud.security.token.validation.validators.JwtValidatorBuilder;
 
 public class IntegrationTest {
 
+	public static final Instant NO_EXPIRE_DATE = new GregorianCalendar(2190, 11, 31).getTime().toInstant();
+
+	CloseableHttpClient mockHttpClient;
+
+	@Before
+	public void setup() throws IOException {
+		mockHttpClient = Mockito.mock(CloseableHttpClient.class);
+
+		CloseableHttpResponse response = HttpClientTestFactory
+				.createHttpResponse(IOUtils.resourceToString("/jsonWebTokenKeys.json", UTF_8));
+		when(mockHttpClient.execute(any(HttpGet.class))).thenReturn(response);
+	}
+
 	@Test
 	public void validationFails_withXsuaaCombiningValidator() throws URISyntaxException, IOException {
-		OAuth2ServiceConfiguration configuration = Mockito.mock(OAuth2ServiceConfiguration.class);
-		when(configuration.getUrl()).thenReturn(new URI("https://my.auth.com"));
-		when(configuration.getClientId()).thenReturn("sb-test-app!t123");
-		when(configuration.getProperty(CFConstants.XSUAA.APP_ID)).thenReturn("test-app!t123");
-		when(configuration.getProperty(CFConstants.XSUAA.UAA_DOMAIN)).thenReturn("auth.com");
-		when(configuration.getService()).thenReturn(Service.XSUAA);
+		String vcapServices = IOUtils.resourceToString("/vcapXsuaaServiceSingleBinding.json", UTF_8);
+		JsonObject serviceJsonObject = new DefaultJsonObject(vcapServices).getJsonObjects(Service.XSUAA.getCFName())
+				.get(0);
+		Map<String, String> credentialsMap = serviceJsonObject.getJsonObject(CFConstants.CREDENTIALS).getKeyValueMap();
+
+		OAuth2ServiceConfiguration configuration = OAuth2ServiceConfigurationBuilder.forService(Service.XSUAA)
+				.withProperties(credentialsMap)
+				.build();
+
+		CombiningValidator<Token> tokenValidator = JwtValidatorBuilder.getInstance(configuration)
+				.withHttpClient(mockHttpClient)
+				.build();
 
-		CombiningValidator<Token> tokenValidator = JwtValidatorBuilder.getInstance(configuration).build();
+		Token xsuaaToken = spy(new XsuaaToken(
+				IOUtils.resourceToString("/xsuaaUserAccessTokenRSA256.txt", StandardCharsets.UTF_8)));
+		when(xsuaaToken.getExpiration()).thenReturn(NO_EXPIRE_DATE);
+		when(xsuaaToken.getClaimAsStringList(TokenClaims.AUDIENCE))
+				.thenReturn(Arrays.asList(new String[] { "clientId" }));
 
-		Token xsuaaToken = new XsuaaToken(
-				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");
+		assertThat(result.isValid()).isTrue();
+	}
+
+	@Test
+	public void xsaTokenValidationSucceeds_withXsuaaCombiningValidator() throws IOException {
+		String XsaVcapServices = IOUtils.resourceToString("/vcapXsuaaXsaSingleBinding.json", UTF_8);
+		JsonObject serviceJsonObject = new DefaultJsonObject(XsaVcapServices).getJsonObjects(Service.XSUAA.getCFName())
+				.get(0);
+		Map<String, String> credentialsMap = serviceJsonObject.getJsonObject(CFConstants.CREDENTIALS).getKeyValueMap();
+
+		OAuth2ServiceConfiguration configuration = OAuth2ServiceConfigurationBuilder.forService(Service.XSUAA)
+				.withProperties(credentialsMap)
+				.runInLegacyMode(true)
+				.build();
+
+		CombiningValidator<Token> tokenValidator = JwtValidatorBuilder.getInstance(configuration)
+				.withHttpClient(mockHttpClient)
+				.build();
+
+		XsuaaToken xsaToken = spy(new XsuaaToken(
+				IOUtils.resourceToString("/xsuaaXsaAccessTokenRSA256_signedWithVerificationKey.txt", UTF_8)));
+		when(xsaToken.getExpiration()).thenReturn(NO_EXPIRE_DATE);
+
+		ValidationResult result = tokenValidator.validate(xsaToken);
+		assertThat(result.isValid()).isTrue();
 	}
 }
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 95868813..80532b5f 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
@@ -188,7 +188,7 @@ public class JwtSignatureValidatorTest {
 		when(tokenKeyServiceMock
 				.retrieveTokenKeys(any())).thenThrow(OAuth2ServiceException.class);
 
-		ValidationResult result = cut.validate(xsuaaToken.getAccessToken(), "RS256", null,
+		ValidationResult result = cut.validate(xsuaaToken.getAccessToken(), "RS256", "key-id-1",
 				"http://unavailable.com/token_keys", null);
 		assertThat(result.isErroneous(), is(true));
 		assertThat(result.getErrorDescription(),
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 43b18e75..f70088aa 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
@@ -1,14 +1,14 @@
 package com.sap.cloud.security.token.validation.validators;
 
 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.XSUAA;
+import com.sap.cloud.security.config.cf.CFConstants;
 import com.sap.cloud.security.token.Token;
 import com.sap.cloud.security.token.validation.*;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.junit.MockitoJUnitRunner;
 
@@ -31,7 +31,7 @@ public class JwtValidatorBuilderTest {
 	@Test
 	public void sameServiceConfiguration_getSameInstance() throws URISyntaxException {
 		TokenTestValidator.createValid();
-		OAuth2ServiceConfiguration configuration = configuration();
+		OAuth2ServiceConfiguration configuration = getConfigBuilder().build();
 		JwtValidatorBuilder builder_1 = JwtValidatorBuilder.getInstance(configuration);
 		JwtValidatorBuilder builder_2 = JwtValidatorBuilder.getInstance(configuration);
 		assertThat(builder_1).isSameAs(builder_2);
@@ -40,7 +40,7 @@ public class JwtValidatorBuilderTest {
 	@Test
 	public void withAudienceValidator_overridesXsuaaJwtAudienceValidator() throws URISyntaxException {
 		TokenTestValidator validator = TokenTestValidator.createValid();
-		List<Validator<Token>> validators = JwtValidatorBuilder.getInstance(configuration())
+		List<Validator<Token>> validators = JwtValidatorBuilder.getInstance(getConfigBuilder().build())
 				.withAudienceValidator(validator)
 				.build()
 				.getValidators();
@@ -50,7 +50,7 @@ public class JwtValidatorBuilderTest {
 
 	@Test
 	public void build_containsAllDefaultValidators() throws URISyntaxException {
-		List<Validator<Token>> validators = JwtValidatorBuilder.getInstance(configuration()).build()
+		List<Validator<Token>> validators = JwtValidatorBuilder.getInstance(getConfigBuilder().build()).build()
 				.getValidators();
 
 		assertThat(validators)
@@ -60,11 +60,23 @@ public class JwtValidatorBuilderTest {
 				.hasAtLeastOneElementOfType(JwtSignatureValidator.class);
 	}
 
+	@Test
+	public void buildLegacy_containsAllDefaultValidators() {
+		List<Validator<Token>> validators = JwtValidatorBuilder
+				.getInstance(getConfigBuilder().runInLegacyMode(true).build())
+				.build()
+				.getValidators();
+
+		assertThat(validators)
+				.hasAtLeastOneElementOfType(JwtTimestampValidator.class)
+				.hasAtLeastOneElementOfType(JwtSignatureValidator.class);
+	}
+
 	@Test
 	public void buildWithAnotherValidator_containsAddedValidator() throws URISyntaxException {
 		TokenTestValidator tokenValidator = TokenTestValidator.createValid();
 
-		List<Validator<Token>> validators = JwtValidatorBuilder.getInstance(configuration())
+		List<Validator<Token>> validators = JwtValidatorBuilder.getInstance(getConfigBuilder().build())
 				.with(tokenValidator)
 				.build()
 				.getValidators();
@@ -74,13 +86,12 @@ public class JwtValidatorBuilderTest {
 				.contains(tokenValidator);
 	}
 
-	private OAuth2ServiceConfiguration configuration() {
-		OAuth2ServiceConfiguration configuration = mock(OAuth2ServiceConfiguration.class);
-		when(configuration.getClientId()).thenReturn("sb-test-app!t123");
-		when(configuration.getProperty(XSUAA.APP_ID)).thenReturn("test-app!t123");
-		when(configuration.getProperty(XSUAA.UAA_DOMAIN)).thenReturn("auth.com");
-		when(configuration.getService()).thenReturn(Service.XSUAA);
-		return configuration;
+	private OAuth2ServiceConfigurationBuilder getConfigBuilder() {
+		return OAuth2ServiceConfigurationBuilder
+				.forService(Service.XSUAA)
+				.withProperty(CFConstants.XSUAA.UAA_DOMAIN, "auth.com")
+				.withProperty(CFConstants.XSUAA.APP_ID, "test-app!t123")
+				.withClientId("sb-test-app!t123");
 	}
 
 }
\ No newline at end of file
diff --git a/java-security/src/test/resources/jsonWebTokenKeys.json b/java-security/src/test/resources/jsonWebTokenKeys.json
index 25f27211..7e487eeb 100644
--- a/java-security/src/test/resources/jsonWebTokenKeys.json
+++ b/java-security/src/test/resources/jsonWebTokenKeys.json
@@ -23,6 +23,15 @@
       "kid": "key-id-1",
       "alg": "ES256",
       "value": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEQgb5npLHd0Bk61bNnjK632uwmBfr\nF7I8hoPgaOZjyhh+BrPDO6CL6D/aW/yPObXXm7SpZogmRwGROcOA3yUleg==\n-----END PUBLIC KEY-----"
+    },
+    {
+      "kty": "RSA",
+      "e": "AQAB",
+      "use": "sig",
+      "kid": "legacy-token-key",
+      "alg": "RS256",
+      "value": "-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwOzNGEDK6jNrJcl3L2wstY8SnX9KNo4ic/JtrEQX4U/+749b9y6JlSGOXSKBFJ6alNGHVyPK66SfSgeWHwPjVekdnHYM7bQTyYX1NoSOxqUgvsktIpxeYJLC+J5qJTtnFcjFTRUovKwtCYxlTP4b63b7dWixwc+iSnkuvl4IkZjVTcstK73eTlmVtJDC5DIisPyYOK06qxTPrLC8KlgFA6a0FdPJF9G8s/ElxPHFu7mw6UKn2LTmDlDbADQZLMXGKBw/zg3l5scjxkOCVGTkGvv1ooccqCfYgZAsmsPbQdoZxznSdQTPfaWULnYLo8i5hFXiS1q8agvXHjWpUmWcMwIDAQAB-----END PUBLIC KEY-----",
+      "n": "AJjTNzl32UtFLvHmGVwoBlhYFVkF-jB52nWJN8x2eTyD3g2NwKWkhqTBIlcJ9XE-ilFRzCx3Js9YLDcu-KQp5gmttluydwaGbpc0dAN-2sjFa0R4d5334MkpPLufNZdNm723KWm93txKLUjeS4sRk9VVmbw22pV3-p-ZKuOfTVi-mc5BLNtDKzhJOXC3Z7IoE0FB0iiEOU6ZXcg5CTJts8DpawdkffOPkHZQxZqFR-2Gro8a9oNGferu1vSJopOsE4hXPFu3lF34Txp-63lS6tf-aNjc9CcdHoxRw8Exp3LPpNUQUug26UzjK_bZCRHN2bF9xbeDragpEVyOYVJmvh8"
     }
   ]
 }
\ No newline at end of file
diff --git a/java-security/src/test/resources/vcapXsuaaServiceSingleBinding.json b/java-security/src/test/resources/vcapXsuaaServiceSingleBinding.json
index dcd8591f..28c0f716 100644
--- a/java-security/src/test/resources/vcapXsuaaServiceSingleBinding.json
+++ b/java-security/src/test/resources/vcapXsuaaServiceSingleBinding.json
@@ -3,7 +3,7 @@
    {
     "binding_name": null,
     "credentials": {
-     "clientid": "xs2.usertoken",
+     "clientid": "clientId",
      "clientsecret": "secret",
      "identityzone": "uaa",
      "identityzoneid": "uaa",
diff --git a/java-security/src/test/resources/vcapXsuaaXsaSingleBinding.json b/java-security/src/test/resources/vcapXsuaaXsaSingleBinding.json
new file mode 100644
index 00000000..7027bcad
--- /dev/null
+++ b/java-security/src/test/resources/vcapXsuaaXsaSingleBinding.json
@@ -0,0 +1,18 @@
+{
+  "xsuaa" : [ {
+    "name" : "java-uaa",
+    "label" : "xsuaa",
+    "tags" : [ "xsuaa" ],
+    "plan" : "space",
+    "credentials" : {
+      "tenantmode" : "dedicated",
+      "clientid" : "sb-java-hello-world!i1",
+      "verificationkey" : "-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwOzNGEDK6jNrJcl3L2wstY8SnX9KNo4ic/JtrEQX4U/+749b9y6JlSGOXSKBFJ6alNGHVyPK66SfSgeWHwPjVekdnHYM7bQTyYX1NoSOxqUgvsktIpxeYJLC+J5qJTtnFcjFTRUovKwtCYxlTP4b63b7dWixwc+iSnkuvl4IkZjVTcstK73eTlmVtJDC5DIisPyYOK06qxTPrLC8KlgFA6a0FdPJF9G8s/ElxPHFu7mw6UKn2LTmDlDbADQZLMXGKBw/zg3l5scjxkOCVGTkGvv1ooccqCfYgZAsmsPbQdoZxznSdQTPfaWULnYLo8i5hFXiS1q8agvXHjWpUmWcMwIDAQAB-----END PUBLIC KEY-----",
+      "xsappname" : "java-hello-world!i1",
+      "identityzone" : "uaa",
+      "identityzoneid" : "uaa",
+      "clientsecret" : "fxnWLHqLh6KC0Wp/bbv8Gwbu50OEbpSvVYTiI/kZILsR/dYH8YL2V5tP1buzke9QmqK9AruntTAI\nKu4enzCTNw==",
+      "url" : "https://xsa-test.c.eu-de-2.cloud.sap:30132/uaa-security"
+  }
+  } ]
+}
\ No newline at end of file
diff --git a/java-security/src/test/resources/xsuaaEmptyToken.txt b/java-security/src/test/resources/xsuaaEmptyToken.txt
new file mode 100644
index 00000000..5b70afa7
--- /dev/null
+++ b/java-security/src/test/resources/xsuaaEmptyToken.txt
@@ -0,0 +1,1 @@
+eyJhbGciOiJSUzI1NiJ9.e30=.Fix5uWgo17FnuwSmkpvLG19KatPjL6yjzPdqu7Ny2IG43B7gT_jcxFDDVTaqYkCWyKv4QfbvtZCtWT9dhkCyeW49Pg2lRRNotLn4mv8FprIPRP_rvY0ryNpYjGlQAcY7pFjezPYvE9saxQtV7iqTCUx1jZ6KvP4e020Q7egmZM_Rtu8lA6pofeeaVJ6awpKFAeY0Gk_U5BlKcBZlUE0sZK_UmrTkHFXQk2xv5lE_Illim24LeC8LeJgYp2tWTvkKFx7xEKwkHnlE-DWqOnO1tQ29HgWhU65pOsrSwgKDG80QoVA1qnsYT8NHtOkR9hZuSsA8nIS3uExnUFx8aHlSEQ==
\ No newline at end of file
diff --git a/java-security/src/test/resources/xsuaaScopesTokenRSA256.txt b/java-security/src/test/resources/xsuaaScopesTokenRSA256.txt
deleted file mode 100644
index f6f21fda..00000000
--- a/java-security/src/test/resources/xsuaaScopesTokenRSA256.txt
+++ /dev/null
@@ -1,1 +0,0 @@
-eyJhbGciOiJSUzI1NiJ9.eyJzY29wZSI6WyJvcGVuaWQiLCJhcHAxLnNjb3BlIl19.mXL5Zq4siZoxzpX0XW68rqLBXsxIE3fvf8x7HEg6QDe8UX5gm3vpH_b_eLqIWmOQczQvcKpx4hPIfmMOk38pwr2T8OK6STZDgOp4xFbQlNF0Ak1zEWwpnGVMxMMjqyG76J5dkynGCsKNTd0IxaXZS79zbo8og0RT1So4xUTNo84XKPkSUQjveo-Es5_J6ha3W6gBCnjem6L8eKLcPkiibtyYyWMcBHOmm5LYz5rgb2mjpgvTYQW6rrs4CoNRlKpKAPz7CweKIqiCxqqdU6Wac9G7QzmX3AV4T377RkokGqj395UzmWAWIxRWBmClnFd8jCS89qwyHKgp-pk0e8qQfA==
\ No newline at end of file
diff --git a/java-security/src/test/resources/xsuaaUserInfoAdapterToken.txt b/java-security/src/test/resources/xsuaaUserInfoAdapterToken.txt
new file mode 100644
index 00000000..448927a2
--- /dev/null
+++ b/java-security/src/test/resources/xsuaaUserInfoAdapterToken.txt
@@ -0,0 +1,1 @@
+eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJkMjFmNWRlOS1kNzYxLTQ3YTItYjZkNC0yZDgzMTYxNTg0ZDkiLCJhel9hdHRyIjp7ImV4dGVybmFsX2lkIjoiYWJjZDEyMzQifSwieHMudXNlci5hdHRyaWJ1dGVzIjp7InVzckF0dHIiOlsidGVzdCJdfSwidXNlcl9uYW1lIjoiVGVzdFVzZXIiLCJvcmlnaW4iOiJ1c2VyaWRwIiwiaXNzIjoiaHR0cDovL3BhYXMubG9jYWxob3N0OjgwODAvdWFhL29hdXRoL3Rva2VuIiwieHMuc3lzdGVtLmF0dHJpYnV0ZXMiOnsieHMuc2FtbC5ncm91cHMiOlsiZzEiXSwieHMucm9sZWNvbGxlY3Rpb25zIjpbXX0sImdpdmVuX25hbWUiOiJUZXN0VXNlciIsImNsaWVudF9pZCI6Im15Q2xpZW50SWQiLCJhdWQiOiJbXSIsImV4dF9hdHRyIjp7ImVuaGFuY2VyIjoiWFNVQUEiLCJ6ZG4iOiJwYWFzIiwiYWNsIjpbImFwcDEhdDIzIl0sInNlcnZpY2VpbnN0YW5jZWlkIjoiYnJva2VyQ2xvbmVTZXJ2aWNlSW5zdGFuY2VJZCJ9LCJ6aWQiOiJwYWFzIiwiZ3JhbnRfdHlwZSI6InVybjppZXRmOnBhcmFtczpvYXV0aDpncmFudC10eXBlOnNhbWwyLWJlYXJlciIsInVzZXJfaWQiOiJkMjFmNWRlOS1kNzYxLTQ3YTItYjZkNC0yZDgzMTYxNTg0ZDkiLCJhenAiOiJzYi1jbG9uZTEhYjV8TFItbWFzdGVyIWI1Iiwic2NvcGUiOlsib3BlbmlkIiwidGVzdFNjb3BlIiwidGVzdEFwcC5sb2NhbFNjb3BlIl0sImV4cCI6IjE1Nzk2OTYzODIiLCJmYW1pbHlfbmFtZSI6InVua25vd24ub3JnIiwiaWF0IjoiMTUzMjQxNjg0OSIsImp0aSI6ImNmMzQ4MWRiNjQ5YTRhYTA4YjVlZjNjMjJmN2FiOTRmIiwiZW1haWwiOiJUZXN0VXNlckB1YWEub3JnIiwicmV2X3NpZyI6ImI4NTA3NTZhIiwiY2lkIjoic2ItY2xvbmUxIWI1fExSLW1hc3RlciFiNSJ9.d1lS2889TKWejuzBHSNnfKAUJjJxbQF0nDovez5sq9BrLKSobH3C4dT-aiko6r78pfOa2HA1e9VRCyeI0Kj2cCOnFHbVSeIUpjJja0j8nZscA9TAYmWDOZcmMmUIa-StdJD8uZWN78QvTjHc8y0vQ2ohV45xJs9uPmpLFABKGYPJjasWUMK2CZ9lEcKcw8ANcuGte4Ss8tklQDF69pXjGOfgGb1sKUDCiQdoaMHi23dMx6-6yW4SEPpyErhQNk4j3qGHzn7pwfuybZbLwL68iZlvph-B1iLmw9vmA1Ix6njnJy5NrLKX14bnKJobMdgRQ2jEDRs-DKb6_sfjZwFhKQ==
\ No newline at end of file
diff --git a/java-security/src/test/resources/xsuaaXsaAccessTokenRSA256_signedWithVerificationKey.txt b/java-security/src/test/resources/xsuaaXsaAccessTokenRSA256_signedWithVerificationKey.txt
new file mode 100644
index 00000000..451458c2
--- /dev/null
+++ b/java-security/src/test/resources/xsuaaXsaAccessTokenRSA256_signedWithVerificationKey.txt
@@ -0,0 +1,1 @@
+eyJhbGciOiAiUlMyNTYiLCJ0eXAiOiAiSldUIn0.eyJqdGkiOiI1MmEzMzU2ZmRkYzQ0YTM1OTU4M2IwZjg0YmJiNDllZCIsInhzLnVzZXIuYXR0cmlidXRlcyI6e30sImV4dF9hdHRyIjp7ImVuaGFuY2VyIjoiWFNVQUEifSwic3ViIjoiMTYwNDk3Iiwic2NvcGUiOlsib3BlbmlkIl0sImNsaWVudF9pZCI6InNiLWphdmEtaGVsbG8td29ybGQhaTEiLCJjaWQiOiJzYi1qYXZhLWhlbGxvLXdvcmxkIWkxIiwiYXpwIjoic2ItamF2YS1oZWxsby13b3JsZCFpMSIsImdyYW50X3R5cGUiOiJhdXRob3JpemF0aW9uX2NvZGUiLCJ1c2VyX2lkIjoiMTYwNDk3Iiwib3JpZ2luIjoidWFhIiwidXNlcl9uYW1lIjoiWFNBX0FETUlOIiwiZW1haWwiOiJYU0FfQURNSU5AWFMxIiwiYXV0aF90aW1lIjoxNTc5NTI2ODQwLCJyZXZfc2lnIjoiZWVhMWNjNWUiLCJpYXQiOjE1Nzk1MjcwODcsImV4cCI6MTU3OTU3MDI4NywiaXNzIjoiaHR0cDovL3hzYS1hMjcyZDg2YS0wZjc0LTQ0OGMtOTNkMS02Yjc4OTAzZDE1NDMvVUFBL29hdXRoL3Rva2VuIiwiemlkIjoidWFhIiwiYXVkIjpbInNiLWphdmEtaGVsbG8td29ybGQhaTEiLCJvcGVuaWQiXX0.EY4Xh2ExTa07C6dkyjm5fwCxAsrV1rv1DfwVfaM0i5BjybVyfVa5F3PnNnSKCwmIMzpVWJ0vQgKJi_3GYTTRfC4UtxRNPIsFLovgyIQ9VbWLJGr0PCcv7-dt0ZXxRQ3mcQQ3NFl2PNVfFz2kk_5hMZZhOIxH00fyc_-BWE1yYndWPHSCJhynbje_RudG7x9McJMNAkcNY9O1wK3pOAnAFUNjM_CEV2Su6AZ6SLSq29o8OR5brI83RXGFPaxudNyTH1pyG4T-JrD8HJAf0KGxRdqDyteGo-bxnorZ57idxE6dBiKr8lBMCv6hJ2Lb0tOEVE66YSBz2BCNrfMZ02ct7Q
\ No newline at end of file