Repository: SAP/cloud-security-xsuaa-integration
Pull request #847 (2022-03-22 17:50:53)

[spring-security] Introduces spring-xsuaa compatibility module with XsuaaTokenComp
Introduces a compatibility module that provides with ``XsuaaTokenComp`` class an option to decorate the token issued by xsuaa.
```xml
<dependency>
    <groupId>com.sap.cloud.security.xsuaa</groupId>
    <artifactId>spring-security-compatibility</artifactId>
    <version>2.11.15</version>
</dependency>
```

``XsuaaTokenComp`` implements `com.sap.cloud.security.xsuaa.token.Token` except for the `UserDetails` methods such as `getAuthorities()` method. Furthermore, `createInstance()` raises an `IllegalArgumentException` exception in case its not a token issued by xsuaa. 

```java
import com.sap.cloud.security.spring.token.SpringSecurityContext; // new
import com.sap.cloud.security.xsuaa.token.Token; // from spring-xsuaa

Token xsuaaToken = XsuaaTokenComp.createInstance(SpringSecurityContext.getAccessToken());
xsuaaToken.getCloneServiceInstanceId();
```

````java
import com.sap.cloud.security.token.Token;

@GetMapping("/comp/sayHello")
public Map<String, String> sayHello_compatibility(@AuthenticationPrincipal Token token) {
      com.sap.cloud.security.xsuaa.token.Token compToken;
      try {
          compToken = XsuaaTokenComp.createInstance(token);
      } catch (IllegalArgumentException e) {
          throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage());
      }
     compToken.getSubdomain();
     ...
}
````

**Migration Guide**<br>
https://github.com/SAP/cloud-security-xsuaa-integration/blob/token-compatibility/spring-security/Migration_SpringXsuaaProjects.md

**Open Todos**
- [x] test e2e
- [x] increase unit test coverage
- [x] add module to parent pom


Patch:
diff --git a/env/pom.xml b/env/pom.xml
index 95e564da..03302180 100644
--- a/env/pom.xml
+++ b/env/pom.xml
@@ -66,7 +66,6 @@
         <dependency>
             <groupId>com.github.tomakehurst</groupId>
             <artifactId>wiremock-jre8</artifactId>
-            <version>2.32.0</version>
             <scope>test</scope>
         </dependency>
         <dependency>
diff --git a/java-api/src/main/java/com/sap/cloud/security/token/AccessToken.java b/java-api/src/main/java/com/sap/cloud/security/token/AccessToken.java
index 9ca96ada..536d6110 100644
--- a/java-api/src/main/java/com/sap/cloud/security/token/AccessToken.java
+++ b/java-api/src/main/java/com/sap/cloud/security/token/AccessToken.java
@@ -84,6 +84,7 @@ public interface AccessToken extends Token {
 	 *         attribute does not exist.
 	 **/
 	@Nullable
+	@Override
 	default String getAttributeFromClaimAsString(String claimName, String attributeName) {
 		return Optional.ofNullable(getClaimAsJsonObject(claimName))
 				.map(claim -> claim.getAsString(attributeName))
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 472e538e..a28aafa2 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
@@ -62,10 +62,13 @@ class JwtSignatureValidator implements Validator<Token> {
 	public ValidationResult validate(Token token) {
 		String jwksUri;
 		String keyId;
+		String zoneIdForTokenKeys = null;
 
-		if (Service.IAS == configuration.getService() && !token.getIssuer().equals("" + configuration.getUrl())
-				&& token.getZoneId() == null) { // lgtm[java/dereferenced-value-may-be-null]
-			return createInvalid("Error occurred during signature validation: OIDC token must provide zone_uuid.");
+		if (Service.IAS == configuration.getService()) {
+			zoneIdForTokenKeys = token.getZoneId();
+			if (!token.getIssuer().equals("" + configuration.getUrl()) && zoneIdForTokenKeys  == null) { // lgtm[java/dereferenced-value-may-be-null]
+				return createInvalid("Error occurred during signature validation: OIDC token must provide zone_uuid.");
+			}
 		}
 		try {
 			jwksUri = getOrRequestJwksUri(token);
@@ -79,7 +82,7 @@ class JwtSignatureValidator implements Validator<Token> {
 					keyId,
 					jwksUri,
 					fallbackPublicKey,
-					token.getZoneId());
+					zoneIdForTokenKeys);
 		} catch (OAuth2ServiceException | IllegalArgumentException e) {
 			return createInvalid("Error occurred during jwks uri determination: {}", e.getMessage());
 		}
@@ -250,7 +253,7 @@ class JwtSignatureValidator implements Validator<Token> {
 					return createValid();
 				}
 				return createInvalid(
-						"Signature of Jwt Token is not valid: the identity provided by the JSON Web Token Key can not be verified.");
+						"Signature of Jwt Token is not valid: the identity provided by the JSON Web Token Key can not be verified (Signature: {}).", tokenHeaderPayloadSignature[2]);
 			} catch (Exception e) {
 				return createInvalid("Error occurred during Json Web Signature Validation: {}.", e.getMessage());
 			}
diff --git a/pom.xml b/pom.xml
index 6385ad02..12519a68 100644
--- a/pom.xml
+++ b/pom.xml
@@ -79,7 +79,7 @@
 		<hamcrest.version>1.3</hamcrest.version>
 		<mockito.version>3.12.4</mockito.version>
 		<assertj.version>3.22.0</assertj.version>
-		<reactor.test.version>3.4.16</reactor.test.version>
+		<reactor.test.version>3.4.17</reactor.test.version>
 		<wiremock.version>2.32.0</wiremock.version>
 		<javax.annotation.version>1.3.2</javax.annotation.version>
 		<spotbugs.annotations.version>4.6.0</spotbugs.annotations.version>
@@ -95,6 +95,7 @@
 		<module>token-client</module>
 		<module>java-security</module>
 		<module>spring-security</module>
+		<module>spring-security-compatibility</module>
 		<module>spring-security-starter</module>
 		<module>java-security-test</module>
 		<module>spring-xsuaa</module>
diff --git a/samples/spring-security-hybrid-usage/pom.xml b/samples/spring-security-hybrid-usage/pom.xml
index 56ae80a5..75a07de6 100644
--- a/samples/spring-security-hybrid-usage/pom.xml
+++ b/samples/spring-security-hybrid-usage/pom.xml
@@ -47,6 +47,11 @@
 			<artifactId>resourceserver-security-spring-boot-starter</artifactId>
 			<version>${sap.cloud.security.version}</version>
 		</dependency>
+		<dependency>
+			<groupId>com.sap.cloud.security.xsuaa</groupId>
+			<artifactId>spring-security-compatibility</artifactId>
+			<version>${sap.cloud.security.version}</version>
+		</dependency>
 		<dependency>
 			<groupId>org.apache.httpcomponents</groupId>
 			<artifactId>httpclient</artifactId>
diff --git a/samples/spring-security-hybrid-usage/src/main/java/sample/spring/security/SecurityConfiguration.java b/samples/spring-security-hybrid-usage/src/main/java/sample/spring/security/SecurityConfiguration.java
index 469100b4..08554275 100644
--- a/samples/spring-security-hybrid-usage/src/main/java/sample/spring/security/SecurityConfiguration.java
+++ b/samples/spring-security-hybrid-usage/src/main/java/sample/spring/security/SecurityConfiguration.java
@@ -46,6 +46,7 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
                 .and()
                     .authorizeRequests()
                     .antMatchers("/sayHello").hasAuthority("Read")
+                    .antMatchers("/comp/sayHello").hasAuthority("Read")
                     .antMatchers("/*").authenticated()
                     .anyRequest().denyAll()
                 .and()
diff --git a/spring-security-compatibility/pom.xml b/spring-security-compatibility/pom.xml
new file mode 100644
index 00000000..45f33f22
--- /dev/null
+++ b/spring-security-compatibility/pom.xml
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- SPDX-FileCopyrightText: 2018-2021 SAP SE or an SAP affiliate company and Cloud Security Client Java contributors -->
+<!-- SPDX-License-Identifier: Apache-2.0 -->
+<project
+        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+        xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>com.sap.cloud.security.xsuaa</groupId>
+        <artifactId>parent</artifactId>
+        <version>2.11.15</version>
+    </parent>
+
+    <groupId>com.sap.cloud.security.xsuaa</groupId>
+    <artifactId>spring-security-compatibility</artifactId>
+    <name>spring-security-compatibility</name>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.sap.cloud.security.xsuaa</groupId>
+            <artifactId>spring-xsuaa</artifactId>
+            <version>${project.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.sap.cloud.security</groupId>
+                    <artifactId>java-api</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>com.sap.cloud.security</groupId>
+            <artifactId>java-api</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- test utilities -->
+        <dependency>
+            <groupId>com.sap.cloud.security</groupId>
+            <artifactId>java-security-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-api</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-params</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-javadoc-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>attach-javadocs</id>
+                        <goals>
+                            <goal>jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-source-plugin</artifactId>
+                <version>${maven.source.plugin.version}</version>
+                <executions>
+                    <execution>
+                        <id>attach-sources</id>
+                        <goals>
+                            <goal>jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.jacoco</groupId>
+                <artifactId>jacoco-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-pmd-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>com.github.spotbugs</groupId>
+                <artifactId>spotbugs-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <reuseForks>false</reuseForks>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/spring-security-compatibility/src/main/java/com/sap/cloud/security/comp/XsuaaTokenComp.java b/spring-security-compatibility/src/main/java/com/sap/cloud/security/comp/XsuaaTokenComp.java
new file mode 100644
index 00000000..8d46ef6a
--- /dev/null
+++ b/spring-security-compatibility/src/main/java/com/sap/cloud/security/comp/XsuaaTokenComp.java
@@ -0,0 +1,365 @@
+/**
+ * SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Cloud Security Client Java contributors
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package com.sap.cloud.security.comp;
+
+import com.sap.cloud.security.config.Service;
+import com.sap.cloud.security.token.AccessToken;
+import com.sap.cloud.security.token.Token;
+import com.sap.cloud.security.token.TokenClaims;
+import org.springframework.security.core.GrantedAuthority;
+
+import javax.annotation.Nullable;
+import java.time.Instant;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Optional;
+
+import static com.sap.cloud.security.token.TokenClaims.XSUAA.EXTERNAL_ATTRIBUTE;
+import static com.sap.cloud.security.token.TokenClaims.XSUAA.EXTERNAL_ATTRIBUTE_ZDN;
+
+/**
+ * Decorates a {@code Token} issued by xsuaa to provide compatibility methods for spring-xsuaa's {@code Token} interface.
+ * @deprecated use methods exposed by the {@link Token} interface.
+ */
+@Deprecated
+public class XsuaaTokenComp implements com.sap.cloud.security.xsuaa.token.Token {
+	private final AccessToken token;
+
+	private XsuaaTokenComp(final Token token) {
+		this.token = (AccessToken) token;
+	}
+
+	/**
+     * Creates an instance.
+     *
+     * @param token a token issued by xsuaa
+	 * @deprecated use methods exposed by the {@link Token} interface.
+     */
+	@Deprecated
+    public static XsuaaTokenComp createInstance(final Token token) {
+		if (Service.XSUAA.equals(token.getService())) {
+			return new XsuaaTokenComp(token);
+		}
+		throw new IllegalArgumentException("The token is not issued by xsuaa service");
+	}
+
+	/**
+	 * Creates an instance.
+	 *
+	 * @param jwtToken
+	 *            the encoded access token, e.g. from the {@code Authorization}
+	 *            header.
+	 * @deprecated use methods exposed by the {@link Token} interface.
+	 */
+	@Deprecated
+	public static XsuaaTokenComp createInstance(final String jwtToken) {
+		Token aToken = Token.create(jwtToken);
+		if (Service.XSUAA.equals(aToken.getService())) {
+			return new XsuaaTokenComp(aToken);
+		}
+		throw new IllegalArgumentException("The token is not issued by xsuaa service");
+	}
+
+	/**
+	 * Return subaccount identifier which is in most cases same like the identity
+	 * zone. DO only use this for metering purposes. DO NOT longer use this method
+	 * to get the unique tenant id! For that use {@link #getZoneId()}.
+	 *
+	 * @return the subaccount identifier.
+	 * @deprecated use {@link AccessToken#getSubaccountId()} instead.
+	 */
+	@Deprecated
+	public String getSubaccountId() {
+		return token.getSubaccountId();
+	}
+
+	/**
+	 * Return zone identifier which should be used as tenant discriminator (tenant
+	 * id). For most of the old subaccounts this matches the id returned by
+	 * {@link #getSubaccountId()}.
+	 *
+	 * @return the zone identifier.
+	 * @deprecated use {@link Token#getZoneId()} instead.
+	 */
+	@Deprecated
+	public String getZoneId() {
+		return token.getZoneId();
+	}
+
+	/**
+	 * Returns the subdomain of the calling tenant's subaccount.
+	 *
+	 * @return the subdomain of the tenant the JWT belongs to.
+	 * @deprecated use {@link Token#getAttributeFromClaimAsString(String, String)} instead
+	 */
+	@Deprecated
+	public String getSubdomain() {
+		return token.getAttributeFromClaimAsString(EXTERNAL_ATTRIBUTE, EXTERNAL_ATTRIBUTE_ZDN);
+	}
+
+	/**
+	 * Returns the OAuth2 client identifier of the authentication token if present.
+	 * Following OpenID Connect 1.0 standard specifications, client identifier is
+	 * obtained from "azp" claim if present or when "azp" is not present from "aud"
+	 * claim, but only in case there is one audience.
+	 *
+	 * @return the OAuth client ID.
+	 * @deprecated use {@link Token#getClientId()} instead.
+	 */
+	@Deprecated
+	public String getClientId() {
+		return token.getClientId();
+	}
+
+	/**
+	 * Returns the OAuth2.0 grant type used for retrieving / creating this token.
+	 *
+	 * @return the grant type
+	 * @deprecated use {@link Token#getGrantType()} instead.
+	 */
+	@Deprecated
+	public String getGrantType() {
+		return token.getGrantType().toString();
+	}
+
+	/**
+	 * Returns a unique user name of a user ({@code user_name} claim), using
+	 * information from the JWT. For tokens that were issued as a result of a client
+	 * credentials flow, the OAuth client ID will be returned in a special format.
+	 * The following information is required to uniquely identify a user: <br>
+	 *
+	 * <ul>
+	 * <li><b>user login name:</b> name of the user in an identity provider,
+	 * provided by this method.
+	 * <li><b>origin:</b> alias to an identity provider, see {@link #getOrigin()}.
+	 * <li><b>zone id:</b> identifier for the zone, see {@link #getZoneId()}.
+	 * </ul>
+	 *
+	 * @return unique principal name or null if it can not be determined.
+	 * @deprecated use {@link Token#getClaimAsString(String)} instead.
+	 */
+	@Nullable
+	@Deprecated
+	public String getLogonName() {
+		return token.getClaimAsString("user_name");
+	}
+
+	/**
+	 * Returns the given name of the user if present. Will try to find it first in
+	 * the {@code ext_attr.given_name} claim before trying to find a
+	 * {@code given_name} claim.
+	 *
+	 * @return the given name if present.
+	 * @deprecated use {@link Token#getClaimAsString(String)} instead
+	 */
+	@Nullable
+	@Deprecated
+	public String getGivenName() {
+		return token.getClaimAsString(TokenClaims.GIVEN_NAME);
+	}
+
+	/**
+	 * Returns the family name of the user if present. Will try to find it first in
+	 * the {@code ext_attr.family_name} claim before trying to find a
+	 * {@code family_name} claim.
+	 *
+	 * @return the family name if present.
+	 * @deprecated use {@link Token#getClaimAsString(String)} instead
+	 */
+	@Nullable
+	@Deprecated
+	public String getFamilyName() {
+		return token.getClaimAsString(TokenClaims.FAMILY_NAME);
+	}
+
+	/**
+	 * Returns the email address of the user, if present.
+	 *
+	 * @return The email address if present.
+	 * @deprecated use {@link Token#getClaimAsString(String)} instead
+	 */
+	@Nullable
+	@Deprecated
+	public String getEmail() {
+		return token.getClaimAsString(TokenClaims.EMAIL);
+	}
+
+	/**
+	 * Returns the user origin. The origin is an alias that refers to a user store
+	 * in 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".
+	 * <p>
+	 * May be null in case this JWT was not created with OAuth 2.0 client
+	 * credentials flow.
+	 *
+	 * @return the user origin if present.
+	 * @deprecated use {@link Token#getClaimAsString(String)} instead
+	 */
+	@Nullable
+	@Deprecated
+	public String getOrigin() {
+		return token.getClaimAsString(TokenClaims.XSUAA.ORIGIN);
+	}
+
+	/**
+     * Returns the value of an attribute from the 'xs.user.attributes' claim.
+     *
+     * @param attributeName
+     *            name of the attribute inside 'xs.user.attributes'.
+     * @return the attribute values array or null if there exists no such attribute.
+	 * @deprecated use {@link Token#getAttributeFromClaimAsStringList(String, String)} (String)} instead
+     */
+    @Nullable
+	@Deprecated
+    public String[] getXSUserAttribute(String attributeName) {
+		return Optional.ofNullable(token.getAttributeFromClaimAsStringList(TokenClaims.XSUAA.XS_USER_ATTRIBUTES, attributeName))
+				.map(values -> values.toArray(new String[] {}))
+				.orElse(new String[] {});
+    }
+
+	/**
+	 * Additional custom authentication attributes included by the OAuth client
+	 * component. Note: this is data controlled by the requester of a token. Might
+	 * be not trustworthy.
+	 *
+	 * @param attributeName
+	 *            name of the authentication attribute
+	 * @return additional attribute value if present.
+	 * @deprecated use {@link Token#getAttributeFromClaimAsString(String, String)} instead
+	 */
+	@Nullable
+	@Deprecated
+	public String getAdditionalAuthAttribute(String attributeName) {
+		return token.getAttributeFromClaimAsString("az_attr", attributeName);
+	}
+
+	/**
+	 * Returns the XSUAA clone instance ID, if present. This will only be set for
+	 * tokens that were issued by an XSUAA with plan broker. Contains the service
+	 * instance id if present.
+	 *
+	 * @return the XSUAA clone service instance id if present.
+	 * @deprecated use {@link Token#getAttributeFromClaimAsString(String, String)} instead
+	 */
+	@Nullable
+	@Deprecated
+	public String getCloneServiceInstanceId() {
+		return token.getAttributeFromClaimAsString(TokenClaims.XSUAA.EXTERNAL_ATTRIBUTE, "serviceinstanceid");
+	}
+
+	/**
+	 * Get the encoded authentication token, e.g. for token forwarding to another
+	 * app.
+	 * <p>
+	 * Never expose this token via log or via HTTP.
+	 *
+	 * @return token
+	 * @deprecated use {@link Token#getTokenValue()} instead
+	 */
+	@Deprecated
+	public String getAppToken() {
+		return token.getTokenValue();
+	}
+
+	/**
+	 * Returns list of scopes with appId prefix, e.g. "&lt;my-app!t123&gt;.Display".
+	 *
+	 * @return all scopes
+	 * @deprecated use {@link Token#getClaimAsStringList(String)} instead
+	 */
+	@Deprecated
+	public Collection<String> getScopes() {
+		return token.getClaimAsStringList(TokenClaims.XSUAA.SCOPES);
+	}
+
+	/**
+	 * @throws UnsupportedOperationException in any case
+	 */
+	@Override
+	@Deprecated
+	public Collection<? extends GrantedAuthority> getAuthorities() {
+		throw new UnsupportedOperationException("does not support methods from org.springframework.security.core.userdetails.UserDetails interface");
+	}
+
+	/**
+	 * @throws UnsupportedOperationException in any case
+	 */
+	@Override
+	@Deprecated
+	public String getPassword() {
+		throw new UnsupportedOperationException("does not support methods from org.springframework.security.core.userdetails.UserDetails interface");
+	}
+
+	/**
+	 * Returns the moment in time when the token is expired.
+	 *
+	 * @return the expiration point in time if present.
+	 * @deprecated use {@link Token#getExpiration()} instead
+	 */
+	@Nullable
+	@Deprecated
+	public Instant getExpiration() {
+		return token.getExpiration();
+	}
+
+	/**
+	 * Returns the moment in time when the token is expired.
+	 *
+	 * @return the expiration point in time if present.
+	 * @deprecated use {@link Token#getExpiration()} instead
+	 */
+	@Deprecated
+	@Nullable
+	public Date getExpirationDate() {
+		return token.getExpiration() != null ? Date.from(token.getExpiration()) : null;
+	}
+
+	/**
+	 * Returns the username used to authenticate the user.
+	 * See {@code import org.springframework.security.core.userdetails.UserDetails#getUsername()}
+	 * @return the username
+	 * @deprecated use {@link Token#getPrincipal()}{@code .getName()} instead
+	 */
+	@Deprecated
+	public String getUsername() {
+		return token.getPrincipal().getName();
+	}
+
+	@Override
+	@Deprecated
+	public boolean isAccountNonExpired() {
+		return true;
+	}
+
+	@Override
+	@Deprecated
+	public boolean isAccountNonLocked() {
+		return true;
+	}
+
+	@Override
+	@Deprecated
+	public boolean isCredentialsNonExpired() {
+		return true;
+	}
+
+	@Override
+	@Deprecated
+	public boolean isEnabled() {
+		return true;
+	}
+
+	/**
+	 * Returns the user name for token.
+	 * @return the user name.
+	 * @deprecated use {@link Token#getPrincipal()}{@code .getName()} instead
+	 */
+	@Deprecated
+	public String toString() {
+		return getUsername();
+	}
+}
diff --git a/spring-security/Migration_SpringXsuaaProjects.md b/spring-security/Migration_SpringXsuaaProjects.md
index afda06aa..f3a2dadb 100644
--- a/spring-security/Migration_SpringXsuaaProjects.md
+++ b/spring-security/Migration_SpringXsuaaProjects.md
@@ -1,6 +1,6 @@
 # Migration Guide for Applications that use spring-xsuaa Security Client Library
 
-This migration guide is a step-by-step guide explaining how to replace the [`spring-xsuaa`](/spring-xsuaa) with this ``spring-security``(/spring-security) Security client library.
+This migration guide is a step-by-step guide explaining how to replace the [`spring-xsuaa`](/spring-xsuaa) with this [`spring-security`](/spring-security) Security client library.
 
 ## Maven Dependencies
 To use the new SAP CP [spring-security](/spring-security) client library the dependencies declared in maven `pom.xml` need to be updated.
@@ -18,14 +18,14 @@ com.sap.cloud.security.xsuaa | xsuaa-spring-boot-starter
 ## Configuration changes
 After the dependencies have been changed, the spring security configuration needs some adjustments as well.
 
-In case you have configured your `TokenAuthenticationConverter` with `setLocalScopeAsAuthorities(true)` then you can use the auto-configured converter instead as  documented [here](/spring-security#setup-spring-security-oauth-20-resource-server):
+In case you have configured your `TokenAuthenticationConverter` with `setLocalScopeAsAuthorities(true)` then you can use the auto-configured converter instead as  documented [here](/spring-security#setup-spring-security-oauth-20-resource-server), which initializes the `XsuaaTokenAuthorizationConverter`:
 ```java
 @Autowired
 Converter<Jwt, AbstractAuthenticationToken> authConverter;
 ```
 
 ## Access VCAP_SERVICES values
-```spring-security``` automatically maps the `VCAP_SERVICES` credentials to Spring properties. Please note that the **prefix has changed** from ```xsuaa.*``` to ```sap.security.services.xsuaa``` or ```sap.security.services.xsuaa[0]``` in case of multiple xsuaa service bindings. Please find some adoption samples here.  
+```spring-security``` automatically maps the `VCAP_SERVICES` credentials to Spring properties. Please note that the **prefix has changed** from ```xsuaa.*``` to ```sap.security.services.xsuaa``` or ```sap.security.services.xsuaa[0]``` in case of multiple xsuaa service bindings. Please find an sample [here](/samples/spring-security-hybrid-usage).  
 
 #### ``@Value``
   **Before**  
@@ -57,7 +57,7 @@ Converter<Jwt, AbstractAuthenticationToken> authConverter;
   XsuaaServiceConfiguration xsuaaServiceConfiguration;
   ```  
 
-#### Bean ``XsuaaServiceConfiguration`` (multiple xsuaa bindings)
+#### Bean ``XsuaaServiceConfigurations`` (multiple xsuaa bindings)
   **Before**  
 
   ```java
@@ -93,13 +93,13 @@ Converter<Jwt, AbstractAuthenticationToken> authConverter;
   ```  
 
   > :bulb: application plan is served as *main* configuration, you can get it using ``xsuaaServiceConfigurations.get(0)``.   
-  > Other configurations, e.g. of plan broker can be accessed with index >= 0.
+  > Other configurations, e.g. of plan broker, can be accessed with index > 0.
 
 
 ## Fetch data from token
 
 #### ``SpringSecurityContext``
-You may have code parts that uses the `SpringSecurityContext` to get the token. Just update the import from:
+You may have code parts that use the `SpringSecurityContext` to get the token. Just update the import from:
 ````java
 import com.sap.cloud.security.xsuaa.token.SpringSecurityContext;
 ````
@@ -109,45 +109,68 @@ import com.sap.cloud.security.spring.token.SpringSecurityContext; // new import
 ````
 
 #### `Token` methods
-You may have code parts that uses the `Token` interface to access details from the token. You need to update the imports from:
+You may have code parts that use the `Token` interface to access details from the token. You need to update the import from:
 ````java
 import com.sap.cloud.security.xsuaa.token.Token;
 ````
 to
 ````java
-import com.sap.cloud.security.token.Token; // new import
-````
+import com.sap.cloud.security.token.Token; // new import - preferred
+// or
+import com.sap.cloud.security.token.XsuaaTokenComp; // new import
+`````
+
+The ``Token`` interface from ``java-api`` provides methods that serves access to details of a token, independent whether it is issued by xsuaa or by identity service. That's why the new ``Token`` interface isn't compatible to the ``Token`` interface from ``spring-xsuaa`` client library.<br>
+``java-api`` provides also the ```AccessToken``` sub-interface to access xsuaa specific claims, in case of access tokens issued by the xsuaa service.
+
+![](images/TokenInterfaces.drawio.svg)
+
+**Alternatively - for compatibility, ``XsuaaTokenComp`` class can be used to decorate the `AccessToken` interface, which implements almost all methods of ` com.sap.cloud.security.xsuaa.token.Token` (spring-xsuaa) interface**. <br>
 
-The ``Token`` interface from ``spring-security`` needs to provide methods that can be served by both kind of tokens. That's why they are not compatible.
-It provides two sub-interfaces: 
-- ```AccessToken``` interface in case of access token issued by the xsuaa service, or 
-- ```SapIdToken``` interface in case of oidc token issued by the identity service.
+It is provided in an extra module. This maven dependency needs to be provided additionally:
+```xml
+<dependency>
+    <groupId>com.sap.cloud.security.xsuaa</groupId>
+    <artifactId>spring-security-compatibility</artifactId>
+    <version>2.11.15</version>
+</dependency>
+```
+
+```java
+Token xsuaaToken = XsuaaTokenComp.createInstance(SpringSecurityContext.getAccessToken());
+xsuaaToken.getCloneServiceInstanceId();
+```
 
-See the following table for methods that are not directly available in the target ```Token``` interface. 
+> :bulb: `createInstance` raises an `IllegalArgumentException` runtime exception if the token isn't issued by xsuaa service. 
+> :bulb: `getAuthorities`, as well as the `org.springframework.security.core.userdetails.UserDetails` is not implemented by `XsuaaTokenComp`.
 
+The table below gives an overview of the methods that are not directly available in the new ```Token``` interface, but can be accessed via the ``XsuaaTokenComp`` decorator class.
 
-| `com.sap.cloud.security.xsuaa.token.Token` methods  | Xsuaa? | Workaround in `spring.security` (`com.sap.cloud.security.token.Token`) |
+<details><br>
+ 
+| `com.sap.cloud.security.xsuaa.token.Token` methods  | Xsuaa only? | Workaround in `spring.security` (`com.sap.cloud.security.token.Token`) |
 |-------------------------|---|-----------------------------------------------------------------------------------------------|
 | `getSubaccountId`       | X | Available via `AccessToken` interface.     
-| `getSubdomain`          | X | Available via `XsuaaToken` implementation. 
-| `getGrantType`          | X | Available via `AccessToken` interface: `getGrantType().toString()`.
-| `getLogonName`          |   | ``getPrincipal().getName()``. :bulb: the name differs between the two services.
+| `getSubdomain`          | X | `getAttributeFromClaimAsString(EXTERNAL_ATTRIBUTE, EXTERNAL_ATTRIBUTE_ZDN)`
+| `getGrantType`          | X | `getGrantType().toString()`.
+| `getLogonName`          | (X) | ``getClaimAsString("user_name")``. :bulb: the name differs between the two services.
 | `getOrigin`             | X | ``getClaimAsString(TokenClaims.XSUAA.ORIGIN)``.
 | `getGivenName`          |   | ``getClaimAsString(TokenClaims.GIVEN_NAME)``. :bulb: no support for SAML 2.0 - XSUAA mapping.
 | `getFamilyName`         |   | ``getClaimAsString(TokenClaims.FAMILY_NAME)``. :bulb: no support for SAML 2.0 - XSUAA mapping.
 | `getEmail`              |   | ``getClaimAsString(TokenClaims.EMAIL)``. :bulb: no support for SAML 2.0 - XSUAA mapping.
-| `getXSUserAttribute`    | X | Available via ```getAttributeFromClaimAsStringList(TokenClaims.XS_USER_ATTRIBUTES, attributeName)```
-| `getAdditionalAuthAttribute`  | X | Available via ```getAttributeFromClaimAsString("az_attr", attributeName)```
-| `getCloneServiceInstanceId`   | X | Available via ```getAttributeFromClaimAsString(TokenClaims.EXTERNAL_ATTRIBUTE, "serviceinstanceid")```
+| `getXSUserAttribute`    | X | ```getAttributeFromClaimAsStringList(TokenClaims.XSUAA.XS_USER_ATTRIBUTES, attributeName)```
+| `getAdditionalAuthAttribute`  | X | ```getAttributeFromClaimAsString("az_attr", attributeName)```
+| `getCloneServiceInstanceId`   | X | ```getAttributeFromClaimAsString(TokenClaims.XSUAA.EXTERNAL_ATTRIBUTE, "serviceinstanceid")```
 | `getAppToken`           |   | `getTokenValue`
 | `getScopes`             | X | `getClaimAsStringList(TokenClaims.XSUAA.SCOPES)`
-| `getAuthorities()`      |   | ./.
 | `getExpiration()`       |   | `getExpiration()` and for convenience `isExpired()`
+| `getUsername()`         |   | `getPrincipal()`
 
-> :bulb: In case the ```Xsuaa?``` flag is set, the method returns "null" in case of Id token from identity service.  
-> :bulb: In case of Id token from identity service, the ``Token`` can neither be casted to `AccessToken` nor `XsuaaToken`.  A cast is possible in case of: ```Service.XSUAA.equals(token.getService())```.   
-> :bulb: `getAttributeFromClaimAsStringList` and `getAttributeFromClaimAsString` are available on `Token` interface as of `java-security` version `2.8.5`.
+> :bulb: In case the ```Xsuaa only?``` flag is set, the method returns "null" in case of Id token from identity service.  
+> :bulb: In case of Id token from identity service, the ``Token`` can neither be casted to `AccessToken` nor to `XsuaaToken`.  A cast is possible in case of: ```Service.XSUAA.equals(token.getService())```.   
 
+</details>
+ 
 #### Spring's `Jwt` methods
 
 The runtime type of `com.sap.cloud.security.xsuaa.token.Token` is `com.sap.cloud.security.xsuaa.token.XsuaaToken`, which provides additional methods that can be used to extract data from the token since it is a subclass of
diff --git a/spring-security/images/TokenInterfaces.drawio.svg b/spring-security/images/TokenInterfaces.drawio.svg
new file mode 100644
index 00000000..f239f859
--- /dev/null
+++ b/spring-security/images/TokenInterfaces.drawio.svg
@@ -0,0 +1,1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="681px" height="441px" viewBox="-0.5 -0.5 681 441" content="&lt;mxfile host=&quot;drawio-plugin&quot; modified=&quot;2022-04-08T15:11:40.007Z&quot; agent=&quot;5.0 (Macintosh; Intel Mac OS X 10_16_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.113 Safari/537.36&quot; etag=&quot;7QgPQFrPKhYcoghSO6Bb&quot; version=&quot;13.7.9&quot; type=&quot;embed&quot;&gt;&lt;diagram id=&quot;C5RBs43oDa-KdzZeNtuy&quot; name=&quot;Page-1&quot;&gt;7Vtbd9o4EP41nNN9IMcXIOQRQ0nTze5JQ9ukj8IWoEa2WEsEyK/fkS0bjBQwEEJbeAKN5ZE93zfSXKDitsPZdYzGo39YgGnFsYJZxe1UHMd26k34kJJ5Krm0a6lgGJNATVoIeuQFK6GlpBMSYF6YKBijgoyLQp9FEfZFQYbimE2L0waMFlcdoyHWBD0fUV36QAIxSqVN53Ih/4TJcJStbDeu0ishyiarN+EjFLDpksj9WHHbMWMi/RbO2phK42V2ebiZP9Dbp8b15y/8P/TN+/vrv9+rqbLuNrfkrxDjSOys+uVp0P30vfbzcXzX/TK1rlv9z1V1i/WM6ETZ6yt7wlHFaVQcFyB0vQ8/0TOqojH5S1lBzDPT8ikJKYpg5A1YJHrqChjGQ5QMI/juwxPjGATPOBYEUGmpC4KNQeqPCA1u0ZxN5HtxgfynbOSNWExeQC2icMkGAVyOhSJYzSrM6Mk7QSylMeYw5y4zlp2LbhEXao7PKEVjTvrJA8spIYqHJPKYECzMFLFJFOBAjXL0k4GIwUwZn+T9JSFSUEpr4NkSQRVk15iFWMRzmKKuurai4zxzKwXZdEHm3NdGS0R2LOWnSDnQMNedL3cPDoeiIRghX89xreJ6NX09u2Far1FcDlEAPkICe9KMfJma8GXpVReihLBbkLemkVejKCUJPUciXLBoFbqMqxQPhIGpIQkCqc7jY+STaPhVMrdTtReS2+TGjruQ3Cu7uAmNBBIoZZrkDkV9TO8YJ4IwqT9O53pjRiKRGK/uVeqdRBKLNovgiRFJWIWBwVMsWWzg21r/3sw3hbcBbiO9mq+TqwDztpjWNUyHWHzCKMAxuDSCRXDc4j0Rg5U1tOEdxWZA062niF1Nx06KGNw7oInbj4AGsDHqeBZx88BWbeuiLhF02jC2F+PjgdosuWc09kf1+uHHUzTEYTN8uH9q9a0Oa/tV19ZgHSG+AusZzpJwXtaODadj8tI2RSQ8g1gSxPIH+cFQdE0o9nD8THx8xrEsjhk+x8NRj4MSbyTwpjfBGciyQNadYwOpBz8XFxdnAMsCePmOYY7xkW39XNTQw1HQkmUNGPUpk1mrByKV1iYZNwy7RC5cJu8sJqk4GOIsDce0z6YfFwIvEcCFjClSNCPiUd4L0KWjH5km+N6ZLQ/maqDDty7T5WwS+3gN6W11CkFyD7vWZj7IV1zLBlOiGmOKBHkuloTWJMZ3ku1LSbhVTMLdTG+mIn1LdddyDWaDIsdyi4pSK2iKdsiYzTtMQyPoI58glBR92iwcLxd++FgmWVWO/UlMxLzqw3WwYp9QGJWuBlmnWA0agP+2GWVxYhq32+067fYBq0TNlaJNo2TRJg9CtysSubXiem7TsN7luxeJzJS/3Lwnn1CVaP22sE+ZyEiwNygTGZ+4pp+0foyBSDdgCBT5+EOypem71J8XPL0VonrwVKrO+3aQ6unoCqRp1e+M6T6FovfF1L3aKiD2KeKc+MWteLszc5vwsG4wRf1tIsa6sxLo2TtGjPWr5npFB44YayWaLH8kgs5KjONYVxf13TB0Heu4GOp1hQnHXMcRLH8r444ifBuj9zz2SSLrV/fITvmtLmfd6laX/05ArVJZbsWbIuOqdeE0r5z9KJFNYYMBxweByNYPv5bvY87P7fhfqx3v1Aqu7Jr6pZYp03J2yrR+0Xa8mcPnTGvjxpb7+V4NeRPBDpVp2U0NVdkl8tnYcIKcSCS+A4qG0rQJxcxl3hzGTHGxA5/AeEaxdD5V0hcPh6IeJgCKtwxgOUO5FZRZSPgeUJp7RTqUeet9j2jvJMvth4r2GtZK9FW2rt5ccxr/dmV1M331nPKUg731Lv77lNU1UB9731qt146VAxUJ0EQwrhriOWmydlrEElIdDoKydfA1+dx+GOi/lbxp9U4JgsY7QlDiDxCqDz6TjfJz1WX3trehmFfuyC7b9nZK7p5/Vtdb3y5O7SDeQKxXCsTvf+Ru10o5/q/D0qdb6cfs7rQH+GFWrmc1QN+2RaMpWj1bdm7RwHDxF8F0+uKPlu7H/wE=&lt;/diagram&gt;&lt;/mxfile&gt;"><defs><clipPath id="mx-clip-243-133-202-26-0"><rect x="243" y="133" width="202" height="26"/></clipPath><clipPath id="mx-clip-243-159-202-26-0"><rect x="243" y="159" width="202" height="26"/></clipPath><clipPath id="mx-clip-243-185-202-26-0"><rect x="243" y="185" width="202" height="26"/></clipPath><clipPath id="mx-clip-243-211-202-26-0"><rect x="243" y="211" width="202" height="26"/></clipPath><clipPath id="mx-clip-243-237-202-26-0"><rect x="243" y="237" width="202" height="26"/></clipPath><clipPath id="mx-clip-243-263-202-26-0"><rect x="243" y="263" width="202" height="26"/></clipPath><clipPath id="mx-clip-4-193-152-26-0"><rect x="4" y="193" width="152" height="26"/></clipPath><clipPath id="mx-clip-4-219-152-26-0"><rect x="4" y="219" width="152" height="26"/></clipPath><clipPath id="mx-clip-248-373-192-22-0"><rect x="248" y="373" width="192" height="22"/></clipPath><clipPath id="mx-clip-248-395-192-22-0"><rect x="248" y="395" width="192" height="22"/></clipPath><clipPath id="mx-clip-248-417-192-22-0"><rect x="248" y="417" width="192" height="22"/></clipPath></defs><g><path d="M 239 120 L 239 80 L 449 80 L 449 120" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><path d="M 239 120 L 239 284 L 449 284 L 449 120" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 239 120 L 449 120" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><g fill="#000000" font-family="Helvetica" font-style="italic" pointer-events="none" text-anchor="middle" font-size="12px"><text x="343.5" y="97.5">Token</text><text x="343.5" y="111.5">(java-api)</text></g><path d="M 239 124 L 449 124" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-243-133-202-26-0)" font-size="12px"><text x="244.5" y="145.5">getHeaderParameterAsString</text></g><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-243-159-202-26-0)" font-size="12px"><text x="244.5" y="171.5">hasHeaderParameter</text></g><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-243-185-202-26-0)" font-size="12px"><text x="244.5" y="197.5">getClaim</text></g><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-243-211-202-26-0)" font-size="12px"><text x="244.5" y="223.5">getService</text></g><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-243-237-202-26-0)" font-size="12px"><text x="244.5" y="249.5">getClientId</text></g><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-243-263-202-26-0)" font-size="12px"><text x="244.5" y="275.5">...</text></g><path d="M 344 320 L 344 302 L 344 302 L 344 296.12" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 344 285.12 L 349.5 296.12 L 338.5 296.12 Z" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 0 180 L 0 140 L 160 140 L 160 180" fill="#fff2cc" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 0 180 L 0 240 L 160 240 L 160 180" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 0 180 L 160 180" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><g fill="#000000" font-family="Helvetica" pointer-events="none" text-anchor="middle" font-size="12px"><text x="79.5" y="157.5">XsuaaTokenComp</text><text x="79.5" y="171.5">(spring-security-compatibility)</text></g><path d="M 0 184 L 160 184" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-4-193-152-26-0)" font-size="12px"><text x="5.5" y="205.5">createInstance(Token)</text></g><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-4-219-152-26-0)" font-size="12px"><text x="5.5" y="231.5">createInstance(String)</text></g><path d="M 449 190 L 511.63 190" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 516.88 190 L 509.88 193.5 L 511.63 190 L 509.88 186.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 160 189.5 L 233.63 189.96" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 238.88 189.99 L 231.86 193.45 L 233.63 189.96 L 231.9 186.45 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 190px; margin-left: 188px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; background-color: #ffffff; white-space: nowrap; ">uses</div></div></div></foreignObject><text x="188" y="193" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">uses</text></switch></g><path d="M 244 360 L 244 320 L 444 320 L 444 360" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 244 360 L 244 440 L 444 440 L 444 360" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 244 360 L 444 360" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><g fill="#000000" font-family="Helvetica" font-style="italic" pointer-events="none" text-anchor="middle" font-size="12px"><text x="343.5" y="337.5">AccessToken</text><text x="343.5" y="351.5">(java-api)</text></g><path d="M 244 364 L 444 364" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-248-373-192-22-0)" font-size="12px"><text x="249.5" y="385.5">getScopes</text></g><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-248-395-192-22-0)" font-size="12px"><text x="249.5" y="407.5">hasScope</text></g><g fill="#000000" font-family="Helvetica" pointer-events="none" clip-path="url(#mx-clip-248-417-192-22-0)" font-size="12px"><text x="249.5" y="429.5">hasLocalScope</text></g><path d="M 520 180 L 520 140 L 680 140 L 680 180" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 520 180 L 520 228 L 680 228 L 680 180" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 520 180 L 680 180" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><g fill="#000000" font-family="Helvetica" pointer-events="none" text-anchor="middle" font-size="12px"><text x="599.5" y="157.5">Service</text><text x="599.5" y="171.5">(java-api)</text></g><path d="M 520 184 L 680 184" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 198px; margin-left: 600px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: nowrap; ">XSUAA</div></div></div></foreignObject><text x="600" y="202" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">XSUAA</text></switch></g><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 218px; margin-left: 600px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: nowrap; ">IAS</div></div></div></foreignObject><text x="600" y="222" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">IAS</text></switch></g><path d="M 0 40 L 0 0 L 160 0 L 160 40" fill="#fff2cc" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 0 40 L 0 100 L 160 100 L 160 40" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 0 40 L 160 40" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><g fill="#000000" font-family="Helvetica" font-style="italic" pointer-events="none" text-anchor="middle" font-size="12px"><text x="79.5" y="17.5">Token</text><text x="79.5" y="31.5">(spring-xsuaa)</text></g><path d="M 0 44 L 160 44" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 80 140 L 80 120 L 80 120 L 80 112.12" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 80 101.12 L 85.5 112.12 L 74.5 112.12 Z" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/></g><switch><g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/><a transform="translate(0,-5)" xlink:href="https://desk.draw.io/support/solutions/articles/16000042487" target="_blank"><text text-anchor="middle" font-size="10px" x="50%" y="100%">Viewer does not support full SVG 1.1</text></a></switch></svg>
\ No newline at end of file


Test patch:
diff --git a/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/OAuth2TokenKeyServiceWithCacheTest.java b/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/OAuth2TokenKeyServiceWithCacheTest.java
index 9507d0bb..62ffd84f 100644
--- a/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/OAuth2TokenKeyServiceWithCacheTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/OAuth2TokenKeyServiceWithCacheTest.java
@@ -42,7 +42,7 @@ public class OAuth2TokenKeyServiceWithCacheTest {
 	@Before
 	public void setup() throws IOException {
 		tokenKeyServiceMock = mock(OAuth2TokenKeyService.class);
-		when(tokenKeyServiceMock.retrieveTokenKeys(eq(TOKEN_KEYS_URI), any()))
+		when(tokenKeyServiceMock.retrieveTokenKeys(eq(TOKEN_KEYS_URI), isNotNull()))
 				.thenReturn(IOUtils.resourceToString("/jsonWebTokenKeys.json", StandardCharsets.UTF_8));
 
 		testCacheTicker = new TestCacheTicker();
@@ -84,10 +84,13 @@ public class OAuth2TokenKeyServiceWithCacheTest {
 
 	@Test
 	public void retrieveTokenKeys() throws OAuth2ServiceException, InvalidKeySpecException, NoSuchAlgorithmException {
-		PublicKey key = cut.getPublicKey(JwtSignatureAlgorithm.RS256, "key-id-0", TOKEN_KEYS_URI, ZONE_ID);
+		PublicKey key1 = cut.getPublicKey(JwtSignatureAlgorithm.RS256, "key-id-0", TOKEN_KEYS_URI, ZONE_ID);
+		PublicKey key2 = cut.getPublicKey(JwtSignatureAlgorithm.RS256, "key-id-0", TOKEN_KEYS_URI, "other-zone-id");
 
-		assertThat(String.valueOf(key.getAlgorithm())).isEqualTo("RSA");
+		assertThat(String.valueOf(key1.getAlgorithm())).isEqualTo("RSA");
+		assertThat(String.valueOf(key2.getAlgorithm())).isEqualTo("RSA");
 		verify(tokenKeyServiceMock, times(1)).retrieveTokenKeys(eq(TOKEN_KEYS_URI), eq(ZONE_ID));
+		verify(tokenKeyServiceMock, times(1)).retrieveTokenKeys(eq(TOKEN_KEYS_URI), eq("other-zone-id"));
 	}
 
 	@Test
@@ -122,14 +125,27 @@ public class OAuth2TokenKeyServiceWithCacheTest {
 	@Test
 	public void retrieveTokenKeys_afterCacheWasCleared()
 			throws OAuth2ServiceException, InvalidKeySpecException, NoSuchAlgorithmException {
-		cut.getPublicKey(JwtSignatureAlgorithm.RS256, "key-id-0", TOKEN_KEYS_URI, null);
+		cut.getPublicKey(JwtSignatureAlgorithm.RS256, "key-id-0", TOKEN_KEYS_URI, ZONE_ID);
 		cut.clearCache();
+		PublicKey cachedKey = cut.getPublicKey(JwtSignatureAlgorithm.RS256, "key-id-0", TOKEN_KEYS_URI, ZONE_ID);
+
+		assertThat(cachedKey).isNotNull();
+		verify(tokenKeyServiceMock, times(2)).retrieveTokenKeys(eq(TOKEN_KEYS_URI), eq(ZONE_ID));
+	}
+
+	@Test
+	public void getCachedTokenKeys_noZoneId() throws IOException, InvalidKeySpecException, NoSuchAlgorithmException {
+		when(tokenKeyServiceMock.retrieveTokenKeys(eq(TOKEN_KEYS_URI), isNull()))
+				.thenReturn(IOUtils.resourceToString("/jsonWebTokenKeys.json", StandardCharsets.UTF_8));
+		PublicKey key = cut.getPublicKey(JwtSignatureAlgorithm.RS256, "key-id-0", TOKEN_KEYS_URI, null);
 		PublicKey cachedKey = cut.getPublicKey(JwtSignatureAlgorithm.RS256, "key-id-0", TOKEN_KEYS_URI, null);
 
 		assertThat(cachedKey).isNotNull();
-		verify(tokenKeyServiceMock, times(2)).retrieveTokenKeys(eq(TOKEN_KEYS_URI), eq(null));
+		assertThat(cachedKey).isSameAs(key);
+		verify(tokenKeyServiceMock, times(1)).retrieveTokenKeys(eq(TOKEN_KEYS_URI), isNull());
 	}
 
+
 	@Test
 	public void retrieveTokenKeys_doesRequestKeysAgainAfterCacheExpired()
 			throws OAuth2ServiceException, InvalidKeySpecException, NoSuchAlgorithmException {
diff --git a/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/XsuaaJwtSignatureValidatorTest.java b/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/XsuaaJwtSignatureValidatorTest.java
index 3a146f08..890d638a 100644
--- a/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/XsuaaJwtSignatureValidatorTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/XsuaaJwtSignatureValidatorTest.java
@@ -25,8 +25,7 @@ import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.*;
 import static org.mockito.Mockito.when;
 
 public class XsuaaJwtSignatureValidatorTest {
@@ -59,7 +58,7 @@ public class XsuaaJwtSignatureValidatorTest {
 		tokenKeyServiceMock = Mockito.mock(OAuth2TokenKeyService.class);
 		when(tokenKeyServiceMock
 				.retrieveTokenKeys(eq(URI.create("https://authentication.stagingaws.hanavlab.ondemand.com/token_keys")),
-						any()))
+						isNull()))
 								.thenReturn(IOUtils.resourceToString("/jsonWebTokenKeys.json", UTF_8));
 
 		cut = new JwtSignatureValidator(
@@ -124,7 +123,11 @@ public class XsuaaJwtSignatureValidatorTest {
 		ValidationResult result = cut.validate(xsuaaTokenSignedWithVerificationKey);
 		assertThat(result.isErroneous(), is(true));
 		assertThat(result.getErrorDescription(),
-				containsString("Signature of Jwt Token is not valid"));
+				containsString("Signature of Jwt Token is not valid")
+		);
+		assertThat(result.getErrorDescription(),
+				containsString("(Signature: CetA62rQSNRj93S9mqaHrKJyzONKeEKcEJ9O5wObRD_")
+		);
 	}
 
 }
diff --git a/samples/spring-security-hybrid-usage/src/main/java/sample/spring/security/TestController.java b/samples/spring-security-hybrid-usage/src/main/java/sample/spring/security/TestController.java
index 14ec00c3..3aa2a9fe 100644
--- a/samples/spring-security-hybrid-usage/src/main/java/sample/spring/security/TestController.java
+++ b/samples/spring-security-hybrid-usage/src/main/java/sample/spring/security/TestController.java
@@ -8,16 +8,19 @@ package sample.spring.security;
 import java.util.HashMap;
 import java.util.Map;
 
+import com.sap.cloud.security.comp.XsuaaTokenComp;
 import com.sap.cloud.security.token.AccessToken;
 import com.sap.cloud.security.token.Token;
 import com.sap.cloud.security.token.TokenClaims;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.security.core.annotation.AuthenticationPrincipal;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.server.ResponseStatusException;
 
 import static com.sap.cloud.security.config.Service.XSUAA;
 
@@ -67,6 +70,29 @@ public class TestController {
         return result;
     }
 
+    @GetMapping("/comp/sayHello")
+    public Map<String, String> sayHello_compatibility(@AuthenticationPrincipal Token token) {
+        logger.debug("Got the token: {}", token);
+        com.sap.cloud.security.xsuaa.token.Token compToken; // to analyze deprecated methods: XsuaaTokenComp compToken;
+        try {
+            compToken = XsuaaTokenComp.createInstance(token);
+        } catch (IllegalArgumentException e) {
+            throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage());
+        }
+        Map<String, String> result = new HashMap<>();
+        result.put("grant type", compToken.getGrantType());
+        result.put("client id", compToken.getClientId());
+        result.put("subaccount id", compToken.getSubaccountId());
+        result.put("zone id", compToken.getZoneId());
+        result.put("logon name", compToken.getLogonName());
+        result.put("family name", compToken.getFamilyName());
+        result.put("given name", compToken.getGivenName());
+        result.put("email", compToken.getEmail());
+        result.put("scopes", String.valueOf(compToken.getScopes()));
+
+        return result;
+    }
+
     /**
      * An endpoint showing how to use Spring method security. Only if the request principal has the given scope will the
      * method be called. Otherwise a 403 error will be returned.
diff --git a/samples/spring-security-hybrid-usage/src/test/java/sample/spring/security/junitjupiter/TestControllerIasTest.java b/samples/spring-security-hybrid-usage/src/test/java/sample/spring/security/junitjupiter/TestControllerIasTest.java
index b89e3beb..d2615119 100644
--- a/samples/spring-security-hybrid-usage/src/test/java/sample/spring/security/junitjupiter/TestControllerIasTest.java
+++ b/samples/spring-security-hybrid-usage/src/test/java/sample/spring/security/junitjupiter/TestControllerIasTest.java
@@ -51,6 +51,12 @@ class TestControllerIasTest {
 		assertTrue(response.contains("the-zone-id"));
 	}
 
+	@Test
+	void sayHello_compatibility() throws Exception {
+		mvc.perform(get("/comp/sayHello").with(bearerToken(jwt)))
+				.andExpect(status().is5xxServerError());
+	}
+
 	@Test
 	void readData_OK() throws Exception {
 		String response = mvc
diff --git a/samples/spring-security-hybrid-usage/src/test/java/sample/spring/security/junitjupiter/TestControllerXsuaaTest.java b/samples/spring-security-hybrid-usage/src/test/java/sample/spring/security/junitjupiter/TestControllerXsuaaTest.java
index 766a296d..f096fe49 100644
--- a/samples/spring-security-hybrid-usage/src/test/java/sample/spring/security/junitjupiter/TestControllerXsuaaTest.java
+++ b/samples/spring-security-hybrid-usage/src/test/java/sample/spring/security/junitjupiter/TestControllerXsuaaTest.java
@@ -50,6 +50,16 @@ class TestControllerXsuaaTest {
 		assertTrue(response.contains("xsapp!t0815.Read"));
 	}
 
+	@Test
+	void sayHello_compatibility() throws Exception {
+		String response = mvc.perform(get("/comp/sayHello").with(bearerToken(jwt)))
+				.andExpect(status().isOk())
+				.andReturn().getResponse().getContentAsString();
+
+		assertTrue(response.contains("sb-clientId!t0815"));
+		assertTrue(response.contains("xsapp!t0815.Read"));
+	}
+
 	@Test
 	void readData_OK() throws Exception {
 		String response = mvc
diff --git a/spring-security-compatibility/src/test/java/com/sap/cloud/security/comp/XsuaaTokenCompTest.java b/spring-security-compatibility/src/test/java/com/sap/cloud/security/comp/XsuaaTokenCompTest.java
new file mode 100644
index 00000000..a5fe1dcf
--- /dev/null
+++ b/spring-security-compatibility/src/test/java/com/sap/cloud/security/comp/XsuaaTokenCompTest.java
@@ -0,0 +1,249 @@
+package com.sap.cloud.security.comp;
+
+import com.sap.cloud.security.config.Service;
+import com.sap.cloud.security.test.JwtGenerator;
+import com.sap.cloud.security.token.GrantType;
+import com.sap.cloud.security.token.Token;
+import com.sap.cloud.security.token.TokenClaims;
+import org.apache.commons.io.IOUtils;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.io.IOException;
+import java.util.*;
+import java.util.stream.Stream;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Copied from {@code com.sap.cloud.security.xsuaa.token.XsuaaTokenTest}.
+ */
+class XsuaaTokenCompTest {
+    private static final String CLAIM_USER_NAME = "user_name";
+    private com.sap.cloud.security.xsuaa.token.Token token;
+    private Token tokenSAML;
+    private String tokenCC;
+    private JwtGenerator jwtGenerator = null;
+    private final String userName = "testUser";
+    private final String zoneId = "e2f7fbdb-0326-40e6-940f-dfddad057ff3";
+    private static final String CLIENT_ID = "sb-java-hello-world";
+
+    @BeforeEach
+    void setup() throws IOException {
+        jwtGenerator = JwtGenerator.getInstance(Service.XSUAA, CLIENT_ID)
+                .withClaimValue(CLAIM_USER_NAME, userName)
+                .withClaimValue(TokenClaims.EMAIL, userName + "@test.org")
+                .withClaimValue(TokenClaims.XSUAA.ZONE_ID, zoneId)
+                .withClaimValue(TokenClaims.XSUAA.CLIENT_ID, CLIENT_ID)
+                .withClaimValue(TokenClaims.XSUAA.ORIGIN, "userIdp")
+                .withClaimValue(TokenClaims.AUTHORIZATION_PARTY, "client")
+                .withClaimValue(TokenClaims.XSUAA.GRANT_TYPE, GrantType.JWT_BEARER.toString());
+
+        tokenSAML = JwtGenerator.getInstanceFromFile(Service.XSUAA, "/saml.json").createToken();
+        tokenCC = IOUtils.resourceToString("/token_cc.txt", UTF_8);
+    }
+
+    @Test
+    void createInstance_failsForNonXsuaaToken() {
+        Token iasToken = JwtGenerator.getInstance(Service.IAS, CLIENT_ID).createToken();
+        assertThrows(IllegalArgumentException.class, () -> XsuaaTokenComp.createInstance(iasToken));
+        assertThrows(IllegalArgumentException.class,
+                () -> XsuaaTokenComp.createInstance(iasToken.getTokenValue()));
+    }
+
+    @Test
+    void checkBasicJwtWithoutScopes() {
+        token = XsuaaTokenComp.createInstance(jwtGenerator.createToken());
+
+        assertThat(token.getClientId(), is("client"));
+        assertThat(token.getGrantType(), is(GrantType.JWT_BEARER.toString()));
+        assertThat(token.getOrigin(), is("userIdp"));
+        assertThat(token.getFamilyName(), nullValue());
+        assertThat(token.getGivenName(), nullValue());
+        assertThat(token.getEmail(), is("testUser@test.org"));
+        assertThat(token.getSubaccountId(), is(zoneId));
+        //assertThat(token.getAuthorities().size(), is(0));
+        assertThat(token.getExpirationDate(), is(Date.from(JwtGenerator.NO_EXPIRE_DATE)));
+        assertThat(token.getExpiration(), is(JwtGenerator.NO_EXPIRE_DATE));
+        assertThat(token.getAdditionalAuthAttribute("any"), nullValue());
+        assertThat(token.getZoneId(), is(zoneId));
+        assertThat(token.getLogonName(), is(userName));
+    }
+
+    @ParameterizedTest
+    @MethodSource("clientIdTestArguments")
+    void getClientId(String azp, List<String> aud, String expectedClientId) {
+        token = XsuaaTokenComp.createInstance(jwtGenerator
+                .withClaimValue(TokenClaims.AUTHORIZATION_PARTY, azp)
+                .withClaimValues(TokenClaims.AUDIENCE, aud.toArray(new String[]{})).createToken());
+        assertThat(token.getClientId(), is(expectedClientId));
+    }
+
+    private static Stream<Arguments> clientIdTestArguments() {
+        return Stream.of(
+                Arguments.of("azp", Arrays.asList("aud1", "aud2"), "azp"),
+                Arguments.of("azp", Collections.singletonList("aud"), "azp"),
+                Arguments.of("", Arrays.asList("aud1", "aud2"), CLIENT_ID),
+                Arguments.of("", Collections.singletonList("aud"), "aud"),
+                Arguments.of("", Collections.emptyList(), CLIENT_ID),
+                Arguments.of(null, Collections.singletonList("aud"), "aud"),
+                Arguments.of(null, Arrays.asList("aud1", "aud2"), CLIENT_ID),
+                Arguments.of(null, Collections.emptyList(), CLIENT_ID),
+                Arguments.of("   ", Collections.singletonList("aud"), "aud"),
+                Arguments.of("   ", Arrays.asList("aud1", "aud2"), CLIENT_ID),
+                Arguments.of("   ", Collections.emptyList(), CLIENT_ID));
+    }
+
+    @Test
+    void getScopesReturnsAllScopes() {
+        String xsAppName = "my-app-name!t400";
+        String scopeRead = xsAppName + "." + "Read";
+        String scopeWrite = xsAppName + "." + "Write";
+        String scopeOther = "other-app-name!t777.Other";
+
+        jwtGenerator.withClaimValues("scope", scopeWrite, scopeRead, scopeOther);
+
+        token = XsuaaTokenComp.createInstance(jwtGenerator.createToken());
+
+        Collection<String> scopes = token.getScopes();
+        assertThat(scopes.size(), is(3));
+        assertThat(scopes, hasItem(scopeRead));
+        assertThat(scopes, hasItem(scopeWrite));
+        assertThat(scopes, hasItem(scopeOther));
+    }
+
+    @Test
+    void getZoneIdAsTenantGuid() {
+        jwtGenerator.withClaimValue(TokenClaims.XSUAA.ZONE_ID, zoneId);
+
+        token = XsuaaTokenComp.createInstance(jwtGenerator.createToken());
+
+        assertThat(token.getSubaccountId(), is(zoneId));
+        assertThat(token.getZoneId(), is(zoneId));
+    }
+
+    @Test
+    void getSubaccountIdFromSystemAttributes() {
+        assertThat(XsuaaTokenComp.createInstance(tokenSAML).getSubaccountId(), is("test-subaccount"));
+    }
+
+
+    @Test
+    void getUserNameIsUniqueWithOrigin() {
+        token = XsuaaTokenComp.createInstance(jwtGenerator.createToken());
+        assertThat(token.getUsername(), is("user/userIdp/testUser"));
+    }
+
+    @Test
+    void toStringShouldReturnUserName() {
+        token = XsuaaTokenComp.createInstance(jwtGenerator.createToken());
+        assertThat(token.toString(), is(token.getUsername()));
+    }
+
+    @Test
+    void getUserNameReturnsErrorWhenOriginContainsDelimeter() {
+        jwtGenerator.withClaimValue(TokenClaims.XSUAA.ORIGIN, "my/Idp");
+        token = XsuaaTokenComp.createInstance(jwtGenerator.createToken());
+        assertNull(token.getUsername());
+    }
+
+    @Test
+    void getUniquePrincipalNameForOriginAndName() {
+        token = XsuaaTokenComp.createInstance(jwtGenerator.createToken());
+        assertThat(token.toString(), is("user/userIdp/testUser"));
+    }
+
+    @Test
+    void getUniquePrincipalNameRaisesErrorWhenOriginIsNull() {
+        jwtGenerator.withClaimValue(TokenClaims.XSUAA.ORIGIN, (String) null);
+        assertNull(XsuaaTokenComp.createInstance(jwtGenerator.createToken()).toString());
+    }
+
+    @Test
+    void getPrincipalNameReturnUniqueLogonNameWithOrigin() {
+        assertEquals("user/useridp/Mustermann", XsuaaTokenComp.createInstance(tokenSAML).getUsername());
+    }
+
+    @Test
+    void getPrincipalNameReturnUniqueClientId() {
+        assertEquals("client/sb-java-hello-world", XsuaaTokenComp.createInstance(tokenCC).getUsername());
+    }
+
+    @Test
+    void getXsUserAttributeValues() {
+        String[] userAttrValues = XsuaaTokenComp.createInstance(tokenSAML).getXSUserAttribute("cost-center");
+        assertThat(userAttrValues.length, is(2));
+        assertThat(userAttrValues[0], is("0815"));
+        assertThat(userAttrValues[1], is("4711"));
+    }
+
+    @Test
+    void getXsUserAttributeValuesFails() {
+        String[] userAttrValues = XsuaaTokenComp.createInstance(tokenSAML).getXSUserAttribute("costcenter");
+        assertThat(userAttrValues.length, is(0));
+    }
+
+    @Test
+    void getServiceInstanceIdFromExtAttr() {
+        Map<String, String> extAttr = new HashMap<>();
+        extAttr.put("serviceinstanceid", "abcd1234");
+        extAttr.put("zdn", "testsubdomain");
+        jwtGenerator.withClaimValue(TokenClaims.XSUAA.EXTERNAL_ATTRIBUTE, extAttr);
+
+        token = XsuaaTokenComp.createInstance(jwtGenerator.createToken());
+        assertThat(token.getCloneServiceInstanceId(), is("abcd1234"));
+    }
+
+    @Test
+    void getSubdomainFromExtAttr() {
+        Map<String, String> extAttr = new HashMap<>();
+        extAttr.put("serviceinstanceid", "abcd1234");
+        extAttr.put("zdn", "testsubdomain");
+        jwtGenerator.withClaimValue(TokenClaims.XSUAA.EXTERNAL_ATTRIBUTE, extAttr);
+
+        token = XsuaaTokenComp.createInstance(jwtGenerator.createToken());
+        assertThat(token.getSubdomain(), is("testsubdomain"));
+    }
+
+    @Test
+    void getSubdomainFails() {
+        Map<String, String> addAuthAttr = new HashMap<>();
+        addAuthAttr.put("external_group", "domain\\group1");
+        addAuthAttr.put("external_id", "ext-id-abcd1234");
+        assertThat(XsuaaTokenComp.createInstance(jwtGenerator.createToken()).getSubdomain(), nullValue());
+
+        jwtGenerator.withClaimValue("az_attr", addAuthAttr);
+        assertThat(XsuaaTokenComp.createInstance(jwtGenerator.createToken()).getSubdomain(), nullValue());
+    }
+
+    @Test
+    void getAppToken() {
+        token = XsuaaTokenComp.createInstance(jwtGenerator.createToken());
+        assertThat(token.getAppToken(), startsWith("eyJqa3UiOiJodHRwOi8vbG9jYWx"));
+    }
+
+    @Test
+    void unsupportedOperations() {
+        token = XsuaaTokenComp.createInstance(jwtGenerator.createToken());
+
+        assertThrows(UnsupportedOperationException.class, () -> token.getAuthorities());
+        assertThrows(UnsupportedOperationException.class, () -> token.getPassword());
+    }
+
+    @Test
+    void userDetailsOperations() {
+        token = XsuaaTokenComp.createInstance(jwtGenerator.createToken());
+
+        assertTrue(token.isAccountNonExpired());
+        assertTrue(token.isAccountNonLocked());
+        assertTrue(token.isCredentialsNonExpired());
+        assertTrue(token.isEnabled());
+    }
+
+}
\ No newline at end of file
diff --git a/spring-security-compatibility/src/test/resources/saml.json b/spring-security-compatibility/src/test/resources/saml.json
new file mode 100644
index 00000000..522fc945
--- /dev/null
+++ b/spring-security-compatibility/src/test/resources/saml.json
@@ -0,0 +1,57 @@
+{
+  "payload": {
+    "jti": "8e7b3b00-7751-4b46-9b1a-5a46a2a1d5b8",
+    "xs.user.attributes": {
+      "cost-center": [
+        "0815",
+        "4711"
+      ],
+      "country": [
+        "Germany"
+      ]
+    },
+    "xs.system.attributes": {
+      "xs.saml.groups": [
+        "g1"
+      ],
+      "xs.rolecollections": [
+        "rc1"
+      ]
+    },
+    "sub": "1002191",
+    "scope": [
+      "java-hello-world.Display",
+      "openid",
+      "java-hello-world.Delete",
+      "java-hello-world.Create"
+    ],
+    "client_id": "sb-java-hello-world",
+    "cid": "sb-java-hello-world",
+    "azp": "sb-java-hello-world",
+    "grant_type": "authorization_code",
+    "user_id": "1002191",
+    "user_name": "Mustermann",
+    "origin": "useridp",
+    "email": "max@example.com",
+    "iat": 1442912244,
+    "exp": 1442955322,
+    "iss": "http://localhost:8080/uaa/oauth/token",
+    "zid": "11-22-33",
+    "hdb.nameduser.saml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><saml2:Assertion xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"_f946bc7b-9339-4200-b7eb-1bba6580106b\" IssueInstant=\"2015-09-22T08:55:22.755Z\" Version=\"2.0\"><saml2:Issuer>xs2-login-test.cf.sap-cf.com-saml-login</saml2:Issuer><ds:Signature xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\"><ds:SignedInfo><ds:CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/><ds:SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha1\"/><ds:Reference URI=\"#_f946bc7b-9339-4200-b7eb-1bba6580106b\"><ds:Transforms><ds:Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/><ds:Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/></ds:Transforms><ds:DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/><ds:DigestValue>nCBFNaR4yfXHaICtvZh6TVpXmIw=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>CVJvIPgfOY6vmF89TfmpxYmIVAaQ3bmJTajeNNuMEC1XNtJwOzz2OeF6xVV1oZI536KADMyPfNVpDnmuPZAEs6qIG7KVFeX+P13GQX237hmDqx1UdtnM94Ca8yWybtaIxcY+O20PbAEkTZAZrP+rs0mN78AIFNMJa+sIZNbnLuU=</ds:SignatureValue></ds:Signature><saml2:Subject><saml2:NameID Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\">WOLFGANG</saml2:NameID><saml2:SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\"><saml2:SubjectConfirmationData NotOnOrAfter=\"2015-09-22T12:55:22.755Z\"/></saml2:SubjectConfirmation></saml2:Subject><saml2:Conditions NotBefore=\"2015-09-22T08:55:22.755Z\" NotOnOrAfter=\"2015-09-22T12:55:22.755Z\"/><saml2:AuthnStatement AuthnInstant=\"2015-09-22T08:55:22.757Z\" SessionNotOnOrAfter=\"2015-09-22T09:00:22.757Z\"><saml2:AuthnContext><saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml2:AuthnContextClassRef></saml2:AuthnContext></saml2:AuthnStatement></saml2:Assertion>",
+    "aud": [
+      "sb-java-hello-world",
+      "java-hello-world",
+      "openid"
+    ],
+    "az_attr": {
+      "external_group": "domain\\group1",
+      "external_id": "abcd1234"
+    },
+    "ext_attr": {
+      "enhancer": "XSUAA",
+      "serviceinstanceid": "abcd1234",
+      "zdn": "testsubdomain",
+      "subaccountid": "test-subaccount"
+    }
+  }
+}
\ No newline at end of file
diff --git a/spring-security-compatibility/src/test/resources/token_cc.txt b/spring-security-compatibility/src/test/resources/token_cc.txt
new file mode 100644
index 00000000..4fd0f43d
--- /dev/null
+++ b/spring-security-compatibility/src/test/resources/token_cc.txt
@@ -0,0 +1,1 @@
+eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ZTdiM2IwMC03NzUxLTRiNDYtOWIxYS01YTQ2YTJhMWQ1YjgiLCJ4cy51c2VyLmF0dHJpYnV0ZXMiOnsiY29zdCBjZW50ZXIiOlsiMDgxNSIsIjQ3MTEiXSwiY291bnRyeSI6WyJHZXJtYW55Il19LCJzdWIiOiIxMDAyMTkxIiwic2NvcGUiOlsiamF2YS1oZWxsby13b3JsZC5EaXNwbGF5Iiwib3BlbmlkIiwiamF2YS1oZWxsby13b3JsZC5EZWxldGUiLCJqYXZhLWhlbGxvLXdvcmxkLkNyZWF0ZSJdLCJjbGllbnRfaWQiOiJzYi1qYXZhLWhlbGxvLXdvcmxkIiwiY2lkIjoic2ItamF2YS1oZWxsby13b3JsZCIsImF6cCI6InNiLWphdmEtaGVsbG8td29ybGQiLCJncmFudF90eXBlIjoiY2xpZW50X2NyZWRlbnRpYWxzIiwidXNlcl9pZCI6IjEwMDIxOTEiLCJ1c2VyX25hbWUiOiJXT0xGR0FORyIsImVtYWlsIjoiV09MRkdBTkdAdW5rbm93biIsImlhdCI6MTQ0MjkxMjI0NCwiZXhwIjoxNDQyOTU1MzIyLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvdWFhL29hdXRoL3Rva2VuIiwiemlkIjoidWFhIiwiaGRiLm5hbWVkdXNlci5zYW1sIjoiPD94bWwgdmVyc2lvbj1cIjEuMFwiIGVuY29kaW5nPVwiVVRGLThcIj8-PHNhbWwyOkFzc2VydGlvbiB4bWxuczpzYW1sMj1cInVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb25cIiBJRD1cIl9mOTQ2YmM3Yi05MzM5LTQyMDAtYjdlYi0xYmJhNjU4MDEwNmJcIiBJc3N1ZUluc3RhbnQ9XCIyMDE1LTA5LTIyVDA4OjU1OjIyLjc1NVpcIiBWZXJzaW9uPVwiMi4wXCI-PHNhbWwyOklzc3Vlcj54czItbG9naW4tdGVzdC5jZi5zYXAtY2YuY29tLXNhbWwtbG9naW48L3NhbWwyOklzc3Vlcj48ZHM6U2lnbmF0dXJlIHhtbG5zOmRzPVwiaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI1wiPjxkczpTaWduZWRJbmZvPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT1cImh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuI1wiLz48ZHM6U2lnbmF0dXJlTWV0aG9kIEFsZ29yaXRobT1cImh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNyc2Etc2hhMVwiLz48ZHM6UmVmZXJlbmNlIFVSST1cIiNfZjk0NmJjN2ItOTMzOS00MjAwLWI3ZWItMWJiYTY1ODAxMDZiXCI-PGRzOlRyYW5zZm9ybXM-PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09XCJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZVwiLz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT1cImh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuI1wiLz48L2RzOlRyYW5zZm9ybXM-PGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09XCJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjc2hhMVwiLz48ZHM6RGlnZXN0VmFsdWU-bkNCRk5hUjR5ZlhIYUlDdHZaaDZUVnBYbUl3PTwvZHM6RGlnZXN0VmFsdWU-PC9kczpSZWZlcmVuY2U-PC9kczpTaWduZWRJbmZvPjxkczpTaWduYXR1cmVWYWx1ZT5DVkp2SVBnZk9ZNnZtRjg5VGZtcHhZbUlWQWFRM2JtSlRhamVOTnVNRUMxWE50SndPenoyT2VGNnhWVjFvWkk1MzZLQURNeVBmTlZwRG5tdVBaQUVzNnFJRzdLVkZlWCtQMTNHUVgyMzdobURxeDFVZHRuTTk0Q2E4eVd5YnRhSXhjWStPMjBQYkFFa1RaQVpyUCtyczBtTjc4QUlGTk1KYStzSVpOYm5MdVU9PC9kczpTaWduYXR1cmVWYWx1ZT48L2RzOlNpZ25hdHVyZT48c2FtbDI6U3ViamVjdD48c2FtbDI6TmFtZUlEIEZvcm1hdD1cInVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OnVuc3BlY2lmaWVkXCI-V09MRkdBTkc8L3NhbWwyOk5hbWVJRD48c2FtbDI6U3ViamVjdENvbmZpcm1hdGlvbiBNZXRob2Q9XCJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyXCI-PHNhbWwyOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj1cIjIwMTUtMDktMjJUMTI6NTU6MjIuNzU1WlwiLz48L3NhbWwyOlN1YmplY3RDb25maXJtYXRpb24-PC9zYW1sMjpTdWJqZWN0PjxzYW1sMjpDb25kaXRpb25zIE5vdEJlZm9yZT1cIjIwMTUtMDktMjJUMDg6NTU6MjIuNzU1WlwiIE5vdE9uT3JBZnRlcj1cIjIwMTUtMDktMjJUMTI6NTU6MjIuNzU1WlwiLz48c2FtbDI6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PVwiMjAxNS0wOS0yMlQwODo1NToyMi43NTdaXCIgU2Vzc2lvbk5vdE9uT3JBZnRlcj1cIjIwMTUtMDktMjJUMDk6MDA6MjIuNzU3WlwiPjxzYW1sMjpBdXRobkNvbnRleHQ-PHNhbWwyOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkPC9zYW1sMjpBdXRobkNvbnRleHRDbGFzc1JlZj48L3NhbWwyOkF1dGhuQ29udGV4dD48L3NhbWwyOkF1dGhuU3RhdGVtZW50Pjwvc2FtbDI6QXNzZXJ0aW9uPiIsImF1ZCI6WyJzYi1qYXZhLWhlbGxvLXdvcmxkIiwiamF2YS1oZWxsby13b3JsZCIsIm9wZW5pZCJdLCJhel9hdHRyIjp7ImV4dGVybmFsX2dyb3VwIjoiZG9tYWluXFxncm91cDEiLCJleHRlcm5hbF9pZCI6ImFiY2QxMjM0In0sImV4dF9hdHRyIjp7InNlcnZpY2VpbnN0YW5jZWlkIjoiYWJjZDEyMzQifX0.YDVwukfuEsIztMGRNXzamo3kEBbbZzK6vAFw_W6k92Q
\ No newline at end of file
diff --git a/spring-security/src/test/resources/saml.json b/spring-security/src/test/resources/saml.json
new file mode 100644
index 00000000..522fc945
--- /dev/null
+++ b/spring-security/src/test/resources/saml.json
@@ -0,0 +1,57 @@
+{
+  "payload": {
+    "jti": "8e7b3b00-7751-4b46-9b1a-5a46a2a1d5b8",
+    "xs.user.attributes": {
+      "cost-center": [
+        "0815",
+        "4711"
+      ],
+      "country": [
+        "Germany"
+      ]
+    },
+    "xs.system.attributes": {
+      "xs.saml.groups": [
+        "g1"
+      ],
+      "xs.rolecollections": [
+        "rc1"
+      ]
+    },
+    "sub": "1002191",
+    "scope": [
+      "java-hello-world.Display",
+      "openid",
+      "java-hello-world.Delete",
+      "java-hello-world.Create"
+    ],
+    "client_id": "sb-java-hello-world",
+    "cid": "sb-java-hello-world",
+    "azp": "sb-java-hello-world",
+    "grant_type": "authorization_code",
+    "user_id": "1002191",
+    "user_name": "Mustermann",
+    "origin": "useridp",
+    "email": "max@example.com",
+    "iat": 1442912244,
+    "exp": 1442955322,
+    "iss": "http://localhost:8080/uaa/oauth/token",
+    "zid": "11-22-33",
+    "hdb.nameduser.saml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><saml2:Assertion xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"_f946bc7b-9339-4200-b7eb-1bba6580106b\" IssueInstant=\"2015-09-22T08:55:22.755Z\" Version=\"2.0\"><saml2:Issuer>xs2-login-test.cf.sap-cf.com-saml-login</saml2:Issuer><ds:Signature xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\"><ds:SignedInfo><ds:CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/><ds:SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha1\"/><ds:Reference URI=\"#_f946bc7b-9339-4200-b7eb-1bba6580106b\"><ds:Transforms><ds:Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/><ds:Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/></ds:Transforms><ds:DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/><ds:DigestValue>nCBFNaR4yfXHaICtvZh6TVpXmIw=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>CVJvIPgfOY6vmF89TfmpxYmIVAaQ3bmJTajeNNuMEC1XNtJwOzz2OeF6xVV1oZI536KADMyPfNVpDnmuPZAEs6qIG7KVFeX+P13GQX237hmDqx1UdtnM94Ca8yWybtaIxcY+O20PbAEkTZAZrP+rs0mN78AIFNMJa+sIZNbnLuU=</ds:SignatureValue></ds:Signature><saml2:Subject><saml2:NameID Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\">WOLFGANG</saml2:NameID><saml2:SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\"><saml2:SubjectConfirmationData NotOnOrAfter=\"2015-09-22T12:55:22.755Z\"/></saml2:SubjectConfirmation></saml2:Subject><saml2:Conditions NotBefore=\"2015-09-22T08:55:22.755Z\" NotOnOrAfter=\"2015-09-22T12:55:22.755Z\"/><saml2:AuthnStatement AuthnInstant=\"2015-09-22T08:55:22.757Z\" SessionNotOnOrAfter=\"2015-09-22T09:00:22.757Z\"><saml2:AuthnContext><saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml2:AuthnContextClassRef></saml2:AuthnContext></saml2:AuthnStatement></saml2:Assertion>",
+    "aud": [
+      "sb-java-hello-world",
+      "java-hello-world",
+      "openid"
+    ],
+    "az_attr": {
+      "external_group": "domain\\group1",
+      "external_id": "abcd1234"
+    },
+    "ext_attr": {
+      "enhancer": "XSUAA",
+      "serviceinstanceid": "abcd1234",
+      "zdn": "testsubdomain",
+      "subaccountid": "test-subaccount"
+    }
+  }
+}
\ No newline at end of file
diff --git a/spring-security/src/test/resources/token_cc.txt b/spring-security/src/test/resources/token_cc.txt
new file mode 100644
index 00000000..4fd0f43d
--- /dev/null
+++ b/spring-security/src/test/resources/token_cc.txt
@@ -0,0 +1,1 @@
+eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ZTdiM2IwMC03NzUxLTRiNDYtOWIxYS01YTQ2YTJhMWQ1YjgiLCJ4cy51c2VyLmF0dHJpYnV0ZXMiOnsiY29zdCBjZW50ZXIiOlsiMDgxNSIsIjQ3MTEiXSwiY291bnRyeSI6WyJHZXJtYW55Il19LCJzdWIiOiIxMDAyMTkxIiwic2NvcGUiOlsiamF2YS1oZWxsby13b3JsZC5EaXNwbGF5Iiwib3BlbmlkIiwiamF2YS1oZWxsby13b3JsZC5EZWxldGUiLCJqYXZhLWhlbGxvLXdvcmxkLkNyZWF0ZSJdLCJjbGllbnRfaWQiOiJzYi1qYXZhLWhlbGxvLXdvcmxkIiwiY2lkIjoic2ItamF2YS1oZWxsby13b3JsZCIsImF6cCI6InNiLWphdmEtaGVsbG8td29ybGQiLCJncmFudF90eXBlIjoiY2xpZW50X2NyZWRlbnRpYWxzIiwidXNlcl9pZCI6IjEwMDIxOTEiLCJ1c2VyX25hbWUiOiJXT0xGR0FORyIsImVtYWlsIjoiV09MRkdBTkdAdW5rbm93biIsImlhdCI6MTQ0MjkxMjI0NCwiZXhwIjoxNDQyOTU1MzIyLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvdWFhL29hdXRoL3Rva2VuIiwiemlkIjoidWFhIiwiaGRiLm5hbWVkdXNlci5zYW1sIjoiPD94bWwgdmVyc2lvbj1cIjEuMFwiIGVuY29kaW5nPVwiVVRGLThcIj8-PHNhbWwyOkFzc2VydGlvbiB4bWxuczpzYW1sMj1cInVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb25cIiBJRD1cIl9mOTQ2YmM3Yi05MzM5LTQyMDAtYjdlYi0xYmJhNjU4MDEwNmJcIiBJc3N1ZUluc3RhbnQ9XCIyMDE1LTA5LTIyVDA4OjU1OjIyLjc1NVpcIiBWZXJzaW9uPVwiMi4wXCI-PHNhbWwyOklzc3Vlcj54czItbG9naW4tdGVzdC5jZi5zYXAtY2YuY29tLXNhbWwtbG9naW48L3NhbWwyOklzc3Vlcj48ZHM6U2lnbmF0dXJlIHhtbG5zOmRzPVwiaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI1wiPjxkczpTaWduZWRJbmZvPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT1cImh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuI1wiLz48ZHM6U2lnbmF0dXJlTWV0aG9kIEFsZ29yaXRobT1cImh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNyc2Etc2hhMVwiLz48ZHM6UmVmZXJlbmNlIFVSST1cIiNfZjk0NmJjN2ItOTMzOS00MjAwLWI3ZWItMWJiYTY1ODAxMDZiXCI-PGRzOlRyYW5zZm9ybXM-PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09XCJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZVwiLz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT1cImh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuI1wiLz48L2RzOlRyYW5zZm9ybXM-PGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09XCJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjc2hhMVwiLz48ZHM6RGlnZXN0VmFsdWU-bkNCRk5hUjR5ZlhIYUlDdHZaaDZUVnBYbUl3PTwvZHM6RGlnZXN0VmFsdWU-PC9kczpSZWZlcmVuY2U-PC9kczpTaWduZWRJbmZvPjxkczpTaWduYXR1cmVWYWx1ZT5DVkp2SVBnZk9ZNnZtRjg5VGZtcHhZbUlWQWFRM2JtSlRhamVOTnVNRUMxWE50SndPenoyT2VGNnhWVjFvWkk1MzZLQURNeVBmTlZwRG5tdVBaQUVzNnFJRzdLVkZlWCtQMTNHUVgyMzdobURxeDFVZHRuTTk0Q2E4eVd5YnRhSXhjWStPMjBQYkFFa1RaQVpyUCtyczBtTjc4QUlGTk1KYStzSVpOYm5MdVU9PC9kczpTaWduYXR1cmVWYWx1ZT48L2RzOlNpZ25hdHVyZT48c2FtbDI6U3ViamVjdD48c2FtbDI6TmFtZUlEIEZvcm1hdD1cInVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OnVuc3BlY2lmaWVkXCI-V09MRkdBTkc8L3NhbWwyOk5hbWVJRD48c2FtbDI6U3ViamVjdENvbmZpcm1hdGlvbiBNZXRob2Q9XCJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyXCI-PHNhbWwyOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj1cIjIwMTUtMDktMjJUMTI6NTU6MjIuNzU1WlwiLz48L3NhbWwyOlN1YmplY3RDb25maXJtYXRpb24-PC9zYW1sMjpTdWJqZWN0PjxzYW1sMjpDb25kaXRpb25zIE5vdEJlZm9yZT1cIjIwMTUtMDktMjJUMDg6NTU6MjIuNzU1WlwiIE5vdE9uT3JBZnRlcj1cIjIwMTUtMDktMjJUMTI6NTU6MjIuNzU1WlwiLz48c2FtbDI6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PVwiMjAxNS0wOS0yMlQwODo1NToyMi43NTdaXCIgU2Vzc2lvbk5vdE9uT3JBZnRlcj1cIjIwMTUtMDktMjJUMDk6MDA6MjIuNzU3WlwiPjxzYW1sMjpBdXRobkNvbnRleHQ-PHNhbWwyOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkPC9zYW1sMjpBdXRobkNvbnRleHRDbGFzc1JlZj48L3NhbWwyOkF1dGhuQ29udGV4dD48L3NhbWwyOkF1dGhuU3RhdGVtZW50Pjwvc2FtbDI6QXNzZXJ0aW9uPiIsImF1ZCI6WyJzYi1qYXZhLWhlbGxvLXdvcmxkIiwiamF2YS1oZWxsby13b3JsZCIsIm9wZW5pZCJdLCJhel9hdHRyIjp7ImV4dGVybmFsX2dyb3VwIjoiZG9tYWluXFxncm91cDEiLCJleHRlcm5hbF9pZCI6ImFiY2QxMjM0In0sImV4dF9hdHRyIjp7InNlcnZpY2VpbnN0YW5jZWlkIjoiYWJjZDEyMzQifX0.YDVwukfuEsIztMGRNXzamo3kEBbbZzK6vAFw_W6k92Q
\ No newline at end of file
diff --git a/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/DefaultOAuth2TokenKeyServiceTest.java b/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/DefaultOAuth2TokenKeyServiceTest.java
index dbd8bb18..a0efae13 100644
--- a/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/DefaultOAuth2TokenKeyServiceTest.java
+++ b/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/DefaultOAuth2TokenKeyServiceTest.java
@@ -54,7 +54,7 @@ public class DefaultOAuth2TokenKeyServiceTest {
 	}
 
 	@Test
-	public void retrieveTokenKeys_responseNotOk_throwsException() throws IOException {
+	public void retrieveTokenKeysForZone_responseNotOk_throwsException() throws IOException {
 		String errorDescription = "Something went wrong";
 		CloseableHttpResponse response = HttpClientTestFactory
 				.createHttpResponse(errorDescription, HttpStatus.SC_BAD_REQUEST);
@@ -68,6 +68,20 @@ public class DefaultOAuth2TokenKeyServiceTest {
 				.hasMessageContaining("Headers [x-zone_uuid=92768714-4c2e-4b79-bc1b-009a4127ee3c]");
 	}
 
+	@Test
+	public void retrieveTokenKeys_responseNotOk_throwsException() throws IOException {
+		String errorDescription = "Something went wrong";
+		CloseableHttpResponse response = HttpClientTestFactory
+				.createHttpResponse(errorDescription, HttpStatus.SC_BAD_REQUEST);
+		when(httpClient.execute(any())).thenReturn(response);
+
+		assertThatThrownBy(() -> cut.retrieveTokenKeys(TOKEN_KEYS_ENDPOINT_URI, null))
+				.isInstanceOf(OAuth2ServiceException.class)
+				.hasMessageContaining(errorDescription)
+				.hasMessageContaining("'Something went wrong'")
+				.hasMessageContaining("Error retrieving token keys");
+	}
+
 	@Test
 	public void retrieveTokenKeys_tokenEndpointUriIsNull_throwsException() {
 		assertThatThrownBy(() -> cut.retrieveTokenKeys(null, ZONE_UUID))