Repository: SAP/cloud-security-xsuaa-integration
Pull request #314 (2020-06-24 12:30:50)

subaccountid is provided with ext_attr instead of xs.system.attributes


Patch:
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 ac0b77d0..e25ce960 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
@@ -89,8 +89,8 @@ public interface XSUserInfo {
 	String getIdentityZone() throws XSUserInfoException;
 
 	/**
-	 * Return subaccount identifier which is in most cases same like the
-	 * identity zone.
+	 * Return subaccount identifier.
+	 *
 	 * DO only use this for metering purposes.
 	 * DO NOT longer use this method to get the unique tenant id! For that use {@link #getZoneId()}.
 	 *
diff --git a/java-api/src/main/java/com/sap/cloud/security/token/TokenClaims.java b/java-api/src/main/java/com/sap/cloud/security/token/TokenClaims.java
index cfbbbc99..384b3b5d 100644
--- a/java-api/src/main/java/com/sap/cloud/security/token/TokenClaims.java
+++ b/java-api/src/main/java/com/sap/cloud/security/token/TokenClaims.java
@@ -34,6 +34,9 @@ public final class TokenClaims {
 		public static final String ISSUED_AT = "iat";
 		public static final String EXTERNAL_ATTRIBUTE = "ext_attr";
 		public static final String EXTERNAL_ATTRIBUTE_ZDN = "zdn";
+		// new with SECAUTH-806
+		public static final String EXTERNAL_ATTRIBUTE_SUBACCOUNTID = "subaccountid";
+		@Deprecated
 		public static final String EXTERNAL_ATTRIBUTE_ENHANCER = "enhancer";
 
 		public static final String TRUSTED_CLIENT_ID_SUFFIX = "trustedclientidsuffix";
diff --git a/java-security/Migration_SAPJavaBuildpackProjects.md b/java-security/Migration_SAPJavaBuildpackProjects.md
index ea189c38..269a20d5 100644
--- a/java-security/Migration_SAPJavaBuildpackProjects.md
+++ b/java-security/Migration_SAPJavaBuildpackProjects.md
@@ -35,17 +35,10 @@ The above mentioned dependencies should be removed / replaced with this one:
 Furthermore, make sure that you do not refer to any other SAP-internal security library with group-id `com.sap.security` or `com.sap.security.nw.sso.*`. 
 
 #### Congratulation! With that you're Done!
-
-
-### Get stuck with migration
-[Open an issue on Github](https://github.com/SAP/cloud-security-xsuaa-integration/issues/new) and provide details like client-lib / migration guide / issue you’re facing.
-
-### [OPTIONAL] Leverage new API and features
 You can continue [here](Migration_SAPJavaBuildpackProjects_V2.md) to understand what needs to be done to leverage the new `java-api` that is exposed by the SAP Java Buildpack as of version `1.26.1`.
 
-
-
-
+### Sample
+- [J2EE java web servlet sample using SAP Java Buildpack](https://github.com/SAP/cloud-security-xsuaa-integration/tree/master/samples/sap-java-buildpack-api-usage)
 
 
 
diff --git a/java-security/Migration_SAPJavaBuildpackProjects_V2.md b/java-security/Migration_SAPJavaBuildpackProjects_V2.md
index 534f7110..d9cb76a9 100644
--- a/java-security/Migration_SAPJavaBuildpackProjects_V2.md
+++ b/java-security/Migration_SAPJavaBuildpackProjects_V2.md
@@ -9,7 +9,7 @@ This migration document is a step-by-step guide explaining how to leverage the n
 
 Please note, this Migration Guide is only intended for applications, using SAP Java Buildpack. You're using the SAP Java Buildpack if you can find the `sap_java_buildpack` in the deployment descriptor of your application, e.g. in your `manifest.yml` file.
 
-**Before you proceed, make sure you have completed [this guide](Migration_SAPJavaBuildpackProjects.md).**
+Furthermore [this guide](Migration_SAPJavaBuildpackProjects.md) is considered as prerequisite. Before you proceed, make sure you have completed it first.
 
 ## Adapt Maven Dependencies <a name="maven"></a>
 To use the latest API exposed by SAP Java Buildpack version as of version `1.26.1` the dependency declared in maven `pom.xml` needs to be adapted.
@@ -19,7 +19,7 @@ First make sure you have the following dependency defined in your pom.xml:
 <dependency>
     <groupId>com.sap.cloud.security</groupId>
     <artifactId>java-api</artifactId>
-    <version>2.7.2</version>
+    <version>2.6.2</version>
     <scope>provided</scope> <!-- provided with buildpack -->
 </dependency>
 ```
@@ -29,7 +29,7 @@ Now you are ready to **remove** the dependency to the **`api`** by deleting the
 <dependency>
     <groupId>com.sap.cloud.security.xsuaa</groupId>
     <artifactId>api</artifactId>
-    <version>2.7.2</version>
+    <version>2.6.2</version>
     <scope>provided</scope>
 </dependency>
 ```
@@ -72,8 +72,7 @@ public class HelloJavaServlet extends HttpServlet {
 ```
 
 ### Sample using new API
-- [J2EE java web servlet sample using SAP Java Buildpack](https://github.com/SAP/cloud-security-xsuaa-integration/tree/master/samples/sap-java-buildpack-api-usage)
-
+- [J2EE java web servlet sample using SAP Java Buildpack](https://github.com/SAP/cloud-security-xsuaa-integration/tree/UpdatedBuildpackSample/samples/sap-java-buildpack-api-usage)
 
 ## Further References
 - [help.sap.com](https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/ead7ee64f96f4c42bacbf0ae23d4135b.html)
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
index 698a7c85..61c8332b 100644
--- 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
@@ -41,12 +41,9 @@ 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";
-	// new with SECAUTH-806
 	static final String XS_SYSTEM_ATTRIBUTES = "xs.system.attributes";
-	static final String CLAIM_SUBACCOUNT_ID = "subaccountid";
 	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";
 
@@ -127,9 +124,11 @@ public class XSUserInfoAdapter implements XSUserInfo {
 	}
 
 	@Override
+	/**
+	 * "ext_attr": { "enhancer": "XSUAA", "subaccountid": "my-subaccount-1234" },
+	 */
 	public String getSubaccountId() {
-		String subaccountId = getAttributeFromClaimAsString(XS_SYSTEM_ATTRIBUTES, CLAIM_SUBACCOUNT_ID);
-		return (subaccountId == null || "".equals(subaccountId)) ? getClaimValue(ZONE_ID) : subaccountId;
+		return Optional.ofNullable(getExternalAttribute(EXTERNAL_ATTRIBUTE_SUBACCOUNTID)).orElse(getClaimValue(ZONE_ID));
 	}
 
 	@Override
@@ -142,7 +141,7 @@ public class XSUserInfoAdapter implements XSUserInfo {
 	 * "ext_attr": { "enhancer": "XSUAA", "zdn": "paas-subdomain" },
 	 */
 	public String getSubdomain() {
-		return Optional.ofNullable(getExternalAttribute(ZDN)).orElse(null);
+		return Optional.ofNullable(getExternalAttribute(EXTERNAL_ATTRIBUTE_ZDN)).orElse(null);
 	}
 
 	@Override
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 45d0f1b5..a3a920c4 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
@@ -6,7 +6,6 @@ import javax.annotation.Nonnull;
 import java.net.URI;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.Objects;
 
 import static com.sap.cloud.security.config.cf.CFConstants.*;
 
@@ -99,72 +98,44 @@ public class OAuth2ServiceConfigurationBuilder {
 	 * @return the oauth2 service configuration.
 	 */
 	public OAuth2ServiceConfiguration build() {
-		return new OAuth2ServiceConfigurationImpl(properties, service, runInLegacyMode);
-	}
-
-	private class OAuth2ServiceConfigurationImpl implements OAuth2ServiceConfiguration {
-
-		private final Map<String, String> properties;
-		private final boolean runInLegacyMode;
-		private final Service service;
-
-		private OAuth2ServiceConfigurationImpl(@Nonnull Map<String, String> properties,
-				@Nonnull Service service, boolean runInLegacyMode) {
-			this.properties = properties;
-			this.service = service;
-			this.runInLegacyMode = runInLegacyMode;
-		}
-
-		@Override
-		public String getClientId() {
-			return properties.get(CLIENT_ID);
-		}
-
-		@Override
-		public String getClientSecret() {
-			return properties.get(CLIENT_SECRET);
-		}
+		return new OAuth2ServiceConfiguration() {
+
+			@Override
+			public String getClientId() {
+				return properties.get(CLIENT_ID);
+			}
+
+			@Override
+			public String getClientSecret() {
+				return properties.get(CLIENT_SECRET);
+			}
+
+			@Override
+			public URI getUrl() {
+				return URI.create(properties.get(URL));
+			}
+
+			@Override
+			public String getProperty(String name) {
+				return properties.get(name);
+			}
+
+			@Override
+			public boolean hasProperty(String name) {
+				return properties.containsKey(name);
+			}
+
+			@Override
+			public Service getService() {
+				return service;
+			}
+
+			@Override
+			public boolean isLegacyMode() {
+				return runInLegacyMode;
+			}
+		};
 
-		@Override
-		public URI getUrl() {
-			return URI.create(properties.get(URL));
-		}
-
-		@Override
-		public String getProperty(String name) {
-			return properties.get(name);
-		}
-
-		@Override
-		public boolean hasProperty(String name) {
-			return properties.containsKey(name);
-		}
-
-		@Override
-		public Service getService() {
-			return service;
-		}
-
-		@Override
-		public boolean isLegacyMode() {
-			return runInLegacyMode;
-		}
-
-		@Override
-		public boolean equals(Object o) {
-			if (this == o)
-				return true;
-			if (o == null || getClass() != o.getClass())
-				return false;
-			OAuth2ServiceConfigurationImpl that = (OAuth2ServiceConfigurationImpl) o;
-			return runInLegacyMode == that.runInLegacyMode &&
-					properties.equals(that.properties) &&
-					service == that.service;
-		}
-
-		@Override
-		public int hashCode() {
-			return Objects.hash(properties, runInLegacyMode, service);
-		}
 	}
+
 }
\ No newline at end of file
diff --git a/java-security/src/main/java/com/sap/cloud/security/token/XsuaaToken.java b/java-security/src/main/java/com/sap/cloud/security/token/XsuaaToken.java
index 548cdfb5..49e90e07 100644
--- a/java-security/src/main/java/com/sap/cloud/security/token/XsuaaToken.java
+++ b/java-security/src/main/java/com/sap/cloud/security/token/XsuaaToken.java
@@ -159,6 +159,19 @@ public class XsuaaToken extends AbstractToken implements AccessToken {
 		return getAttributeFromClaimAsString(EXTERNAL_ATTRIBUTE, EXTERNAL_ATTRIBUTE_ZDN);
 	}
 
+	/**
+	 * Returns 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 subaccount identifier or {@code null}
+	 */
+	@Nullable
+	public String getSubaccountId() {
+		return Optional.ofNullable(getAttributeFromClaimAsString(EXTERNAL_ATTRIBUTE, EXTERNAL_ATTRIBUTE_SUBACCOUNTID))
+				.orElse(getClaimAsString(ZONE_ID));
+	}
+
 	@Nullable
 	private String getAttributeFromClaimAsString(String claimName, String attributeName) {
 		return Optional.ofNullable(getClaimAsJsonObject(claimName))
diff --git a/samples/sap-java-buildpack-api-usage/README.md b/samples/sap-java-buildpack-api-usage/README.md
index 839e8468..149f2457 100644
--- a/samples/sap-java-buildpack-api-usage/README.md
+++ b/samples/sap-java-buildpack-api-usage/README.md
@@ -42,14 +42,8 @@ cf create-service xsuaa application xsuaa-buildpack -c xs-security.json
 ```
 
 ## Configure the manifest
-
 The [vars](../vars.yml) contains hosts and paths that need to be adopted.
 
-This sample uses the `AccessToken` interface to extract user data from the principal. For this to work the environment
-variable `ENABLE_SECURITY_JAVA_API_V2` is set to `true`. This can be done in the `manifest.yml` file inside the
-configuration block of the `sap-java-buildpack-api-usage` application. With this flag set to `true` the principal from
-`HttpServlet.getUserPrincipal()` will contain an `AccessToken` instead of a `XSUserInfo`.
-
 ## Deploy the application
 Deploy the application using cf push. It will expect 1 GB of free memory quota.
 
diff --git a/samples/sap-java-buildpack-api-usage/manifest.yml b/samples/sap-java-buildpack-api-usage/manifest.yml
index 7ea2000f..a9652a32 100644
--- a/samples/sap-java-buildpack-api-usage/manifest.yml
+++ b/samples/sap-java-buildpack-api-usage/manifest.yml
@@ -16,8 +16,6 @@ applications:
     - sap_java_buildpack
   services:
     - xsuaa-buildpack
-  env:
-    ENABLE_SECURITY_JAVA_API_V2: true
 # Application Router as web server
 - name: approuter-sap-java-buildpack-api-usage
   path: approuter
diff --git a/samples/sap-java-buildpack-api-usage/pom.xml b/samples/sap-java-buildpack-api-usage/pom.xml
index 0d74e24c..b152d500 100755
--- a/samples/sap-java-buildpack-api-usage/pom.xml
+++ b/samples/sap-java-buildpack-api-usage/pom.xml
@@ -12,10 +12,10 @@
 	</properties>
 
 	<dependencies>
-		<!-- [OPTIONAL] only required when you need further user information (AccessToken) from request.getUserPrincipal() -->
+		<!-- [OPTIONAL] only required when you need further user information (XSUserInfo) request.getUserPrincipal() -->
 		<dependency>
-			<groupId>com.sap.cloud.security</groupId>
-			<artifactId>java-api</artifactId>
+			<groupId>com.sap.cloud.security.xsuaa</groupId>
+			<artifactId>api</artifactId>
 			<version>${sap.cloud.security.version}</version>
 			<scope>provided</scope>
 		</dependency>
diff --git a/samples/sap-java-buildpack-api-usage/src/main/java/sample/sapbuildpack/xsuaa/HelloTokenServlet.java b/samples/sap-java-buildpack-api-usage/src/main/java/sample/sapbuildpack/xsuaa/HelloTokenServlet.java
index 4e88dce3..8fe0538e 100644
--- a/samples/sap-java-buildpack-api-usage/src/main/java/sample/sapbuildpack/xsuaa/HelloTokenServlet.java
+++ b/samples/sap-java-buildpack-api-usage/src/main/java/sample/sapbuildpack/xsuaa/HelloTokenServlet.java
@@ -6,16 +6,18 @@
  */
 package sample.sapbuildpack.xsuaa;
 
-import com.sap.cloud.security.token.AccessToken;
-import com.sap.cloud.security.token.TokenClaims;
+import java.io.IOException;
 
+import javax.servlet.ServletException;
 import javax.servlet.annotation.HttpConstraint;
 import javax.servlet.annotation.ServletSecurity;
 import javax.servlet.annotation.WebServlet;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
+
+import com.sap.xsa.security.container.XSUserInfo;
+import com.sap.xsa.security.container.XSUserInfoException;
 
 /**
  * Servlet implementation class HelloTokenServlet
@@ -32,23 +34,28 @@ public class HelloTokenServlet extends HttpServlet {
 	 *      response)
 	 */
 	@Override
-	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
+	protected void doGet(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException {
 		response.setContentType("text/plain");
-		AccessToken accessToken = (AccessToken) request.getUserPrincipal();
-
-		response.getWriter().append("Client ID: ")
-				.append("" + accessToken.getClaimAsString(TokenClaims.XSUAA.CLIENT_ID));
-		response.getWriter().append("\n");
-		response.getWriter().append("Email: ").append("" + accessToken.getClaimAsString(TokenClaims.EMAIL));
-		response.getWriter().append("\n");
-		response.getWriter().append("Family Name: ").append("" + accessToken.getClaimAsString(TokenClaims.FAMILY_NAME));
-		response.getWriter().append("\n");
-		response.getWriter().append("First Name: ").append("" + accessToken.getClaimAsString(TokenClaims.GIVEN_NAME));
-		response.getWriter().append("\n");
-		response.getWriter().append("OAuth Grant Type: ").append("" + accessToken.getGrantType());
-		response.getWriter().append("\n");
-		response.getWriter().append("OAuth Token: ").append("" + accessToken.getTokenValue());
-		response.getWriter().append("\n");
+		XSUserInfo userInfo = (XSUserInfo) request.getUserPrincipal();
+
+		try {
+			response.getWriter().append("Client ID: ").append("" + userInfo.getClientId());
+			response.getWriter().append("\n");
+			response.getWriter().append("Email: ").append("" + userInfo.getEmail());
+			response.getWriter().append("\n");
+			response.getWriter().append("Family Name: ").append("" + userInfo.getFamilyName());
+			response.getWriter().append("\n");
+			response.getWriter().append("First Name: ").append("" + userInfo.getGivenName());
+			response.getWriter().append("\n");
+			response.getWriter().append("OAuth Grant Type: ").append("" + userInfo.getGrantType());
+			response.getWriter().append("\n");
+			response.getWriter().append("OAuth Token: ").append("" + userInfo.getAppToken());
+			response.getWriter().append("\n");
+
+		} catch (XSUserInfoException e) {
+			e.printStackTrace(response.getWriter());
+		}
 
 	}
 
diff --git a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/XsuaaToken.java b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/XsuaaToken.java
index db984f77..a9603fb5 100644
--- a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/XsuaaToken.java
+++ b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/XsuaaToken.java
@@ -32,7 +32,6 @@ public class XsuaaToken extends Jwt implements Token {
 	static final String CLAIM_EXTERNAL_ATTR = "ext_attr";
 	static final String CLAIM_EXTERNAL_CONTEXT = "ext_ctx";
 	// new with SECAUTH-806
-	static final String CLAIM_XS_SYSTEM_ATTRIBUTES = "xs.system.attributes";
 	static final String CLAIM_SUBACCOUNT_ID = "subaccountid";
 	//
 	private static final long serialVersionUID = -836947635254353927L;
@@ -175,8 +174,8 @@ public class XsuaaToken extends Jwt implements Token {
 
 	@Override
 	public String getSubaccountId() {
-		String subaccountId = getStringAttributeFromClaim(CLAIM_SUBACCOUNT_ID, CLAIM_XS_SYSTEM_ATTRIBUTES);
-		return isEmpty(subaccountId) ? getClaimAsString(CLAIM_ZONE_ID) : subaccountId;
+		String externalAttribute = getStringAttributeFromClaim(CLAIM_SUBACCOUNT_ID, CLAIM_EXTERNAL_ATTR);
+		return isEmpty(externalAttribute) ? getClaimAsString(CLAIM_ZONE_ID) : externalAttribute;
 	}
 
 	@Override


Test patch:
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
index ff070e6b..bc2a43a0 100644
--- 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
@@ -282,7 +282,7 @@ public class XSUserInfoAdapterTest {
 
 	@Test
 	public void testGetSubaccountIdFromSystemAttributes() throws XSUserInfoException {
-		String token = "eyJqa3UiOiJodHRwOi8vbG9jYWxob3N0Iiwia2lkIjoiZGVmYXVsdC1raWQiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOlsic2ItY2xpZW50SWQhdDA4MTUiXSwiZXh0X2F0dHIiOnsiZW5oYW5jZXIiOiJYU1VBQSIsInpkbiI6InVhYSIsImFjbCI6WyJhcHAxIXQyMyJdLCJzZXJ2aWNlaW5zdGFuY2VpZCI6ImJyb2tlckNsb25lU2VydmljZUluc3RhbmNlSWQifSwiYXpfYXR0ciI6eyJleHRlcm5hbF9pZCI6ImFiY2QxMjM0In0sImdyYW50X3R5cGUiOiJ1cm46aWV0ZjpwYXJhbXM6b2F1dGg6Z3JhbnQtdHlwZTpzYW1sMi1iZWFyZXIiLCJzY29wZSI6WyJvcGVuaWQiLCJ0ZXN0U2NvcGUiLCJ0ZXN0QXBwLmxvY2FsU2NvcGUiXSwiZW1wdHlfbGlzdCI6W10sInhzLnN5c3RlbS5hdHRyaWJ1dGVzIjp7InN1YmFjY291bnRpZCI6InRlc3Qtc3ViYWNjb3VudCJ9LCJleHAiOjE1NDI0MTY4MDAsImVtYWlsIjoidGVzdEB1YWEub3JnIiwiY2lkIjoic2ItY2xpZW50SWQhdDA4MTUifQ.dbCiJFDooPvJ0Sla3pnVXn5exNcVw7dKPgaDF3w_mFS6HqkzhxSwujy0PHtolHlaD1PztZ-6Lyi_eWqlIP3QjIxXKAkf2b3BcAF6WDRX3p5h8Noql9UDoNvYSNImdQcDWN2cZ5q2TzIv2X46HwW0cFP86oKFykALvp_CHlW1GKeJ8VdSWHoMJlOsdoKrLe68HKkIot1yjLsVr9i5LlsQm1EwR_GijLZeDHQNnYL2i_XWOhz3o6bjlcXicdhUaXEBp9CRvb0zucSYbTWJB5tV4e4i0GShXioX6qJG5NOgBZqsigUEaWACfO7Cmdzbr7AsVTlxb_mww9fF7wSAzrW1zA";
+		String token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImprdSI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzMxOTUvdG9rZW5fa2V5cyIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkifQ.ewogICJqdGkiOiAiOGU3YjNiMDAtNzc1MS00YjQ2LTliMWEtNWE0NmEyYTFkNWI4IiwKICAieHMudXNlci5hdHRyaWJ1dGVzIjogewogICAgImNvc3QtY2VudGVyIjogWwogICAgICAiMDgxNSIsCiAgICAgICI0NzExIgogICAgXSwKICAgICJjb3VudHJ5IjogWwogICAgICAiR2VybWFueSIKICAgIF0KICB9LAogICAgInhzLnN5c3RlbS5hdHRyaWJ1dGVzIjogewogICAgInhzLnNhbWwuZ3JvdXBzIjogWwogICAgICAiZzEiCiAgICBdLAogICAgInhzLnJvbGVjb2xsZWN0aW9ucyI6IFsicmMxIl0KICB9LAogICJzdWIiOiAiMTAwMjE5MSIsCiAgInNjb3BlIjogWwogICAgImphdmEtaGVsbG8td29ybGQuRGlzcGxheSIsCiAgICAib3BlbmlkIiwKICAgICJqYXZhLWhlbGxvLXdvcmxkLkRlbGV0ZSIsCiAgICAiamF2YS1oZWxsby13b3JsZC5DcmVhdGUiCiAgXSwKICAiY2xpZW50X2lkIjogInNiLWphdmEtaGVsbG8td29ybGQiLAogICJjaWQiOiAic2ItamF2YS1oZWxsby13b3JsZCIsCiAgImF6cCI6ICJzYi1qYXZhLWhlbGxvLXdvcmxkIiwKICAiZ3JhbnRfdHlwZSI6ICJhdXRob3JpemF0aW9uX2NvZGUiLAogICJ1c2VyX2lkIjogIjEwMDIxOTEiLAogICJ1c2VyX25hbWUiOiAiTXVzdGVybWFubiIsCiAgIm9yaWdpbiI6ICJ1c2VyaWRwIiwKICAiZW1haWwiOiAibWF4QGV4YW1wbGUuY29tIiwKICAiaWF0IjogMTQ0MjkxMjI0NCwKICAiZXhwIjogMTQ0Mjk1NTMyMiwKICAiaXNzIjogImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC91YWEvb2F1dGgvdG9rZW4iLAogICJ6aWQiOiAiMTEtMjItMzMiLAogICJoZGIubmFtZWR1c2VyLnNhbWwiOiAiPD94bWwgdmVyc2lvbj1cIjEuMFwiIGVuY29kaW5nPVwiVVRGLThcIj8-PHNhbWwyOkFzc2VydGlvbiB4bWxuczpzYW1sMj1cInVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb25cIiBJRD1cIl9mOTQ2YmM3Yi05MzM5LTQyMDAtYjdlYi0xYmJhNjU4MDEwNmJcIiBJc3N1ZUluc3RhbnQ9XCIyMDE1LTA5LTIyVDA4OjU1OjIyLjc1NVpcIiBWZXJzaW9uPVwiMi4wXCI-PHNhbWwyOklzc3Vlcj54czItbG9naW4tdGVzdC5jZi5zYXAtY2YuY29tLXNhbWwtbG9naW48L3NhbWwyOklzc3Vlcj48ZHM6U2lnbmF0dXJlIHhtbG5zOmRzPVwiaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI1wiPjxkczpTaWduZWRJbmZvPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT1cImh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuI1wiLz48ZHM6U2lnbmF0dXJlTWV0aG9kIEFsZ29yaXRobT1cImh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNyc2Etc2hhMVwiLz48ZHM6UmVmZXJlbmNlIFVSST1cIiNfZjk0NmJjN2ItOTMzOS00MjAwLWI3ZWItMWJiYTY1ODAxMDZiXCI-PGRzOlRyYW5zZm9ybXM-PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09XCJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZVwiLz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT1cImh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuI1wiLz48L2RzOlRyYW5zZm9ybXM-PGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09XCJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjc2hhMVwiLz48ZHM6RGlnZXN0VmFsdWU-bkNCRk5hUjR5ZlhIYUlDdHZaaDZUVnBYbUl3PTwvZHM6RGlnZXN0VmFsdWU-PC9kczpSZWZlcmVuY2U-PC9kczpTaWduZWRJbmZvPjxkczpTaWduYXR1cmVWYWx1ZT5DVkp2SVBnZk9ZNnZtRjg5VGZtcHhZbUlWQWFRM2JtSlRhamVOTnVNRUMxWE50SndPenoyT2VGNnhWVjFvWkk1MzZLQURNeVBmTlZwRG5tdVBaQUVzNnFJRzdLVkZlWCtQMTNHUVgyMzdobURxeDFVZHRuTTk0Q2E4eVd5YnRhSXhjWStPMjBQYkFFa1RaQVpyUCtyczBtTjc4QUlGTk1KYStzSVpOYm5MdVU9PC9kczpTaWduYXR1cmVWYWx1ZT48L2RzOlNpZ25hdHVyZT48c2FtbDI6U3ViamVjdD48c2FtbDI6TmFtZUlEIEZvcm1hdD1cInVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OnVuc3BlY2lmaWVkXCI-V09MRkdBTkc8L3NhbWwyOk5hbWVJRD48c2FtbDI6U3ViamVjdENvbmZpcm1hdGlvbiBNZXRob2Q9XCJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyXCI-PHNhbWwyOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj1cIjIwMTUtMDktMjJUMTI6NTU6MjIuNzU1WlwiLz48L3NhbWwyOlN1YmplY3RDb25maXJtYXRpb24-PC9zYW1sMjpTdWJqZWN0PjxzYW1sMjpDb25kaXRpb25zIE5vdEJlZm9yZT1cIjIwMTUtMDktMjJUMDg6NTU6MjIuNzU1WlwiIE5vdE9uT3JBZnRlcj1cIjIwMTUtMDktMjJUMTI6NTU6MjIuNzU1WlwiLz48c2FtbDI6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PVwiMjAxNS0wOS0yMlQwODo1NToyMi43NTdaXCIgU2Vzc2lvbk5vdE9uT3JBZnRlcj1cIjIwMTUtMDktMjJUMDk6MDA6MjIuNzU3WlwiPjxzYW1sMjpBdXRobkNvbnRleHQ-PHNhbWwyOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkPC9zYW1sMjpBdXRobkNvbnRleHRDbGFzc1JlZj48L3NhbWwyOkF1dGhuQ29udGV4dD48L3NhbWwyOkF1dGhuU3RhdGVtZW50Pjwvc2FtbDI6QXNzZXJ0aW9uPiIsCiAgImF1ZCI6IFsKICAgICJzYi1qYXZhLWhlbGxvLXdvcmxkIiwKICAgICJqYXZhLWhlbGxvLXdvcmxkIiwKICAgICJvcGVuaWQiCiAgXSwKICAiYXpfYXR0ciI6IHsKICAgICJleHRlcm5hbF9ncm91cCI6ICJkb21haW5cXGdyb3VwMSIsCiAgICAiZXh0ZXJuYWxfaWQiOiAiYWJjZDEyMzQiCiAgfSwKICAiZXh0X2F0dHIiOiB7CiAgICAic2VydmljZWluc3RhbmNlaWQiOiAiYWJjZDEyMzQiLAogICAgInpkbiI6ICJ0ZXN0c3ViZG9tYWluIiwKICAgICJzdWJhY2NvdW50aWQiOiAidGVzdC1zdWJhY2NvdW50IgogIH0KfQ.yi368lAzQi-mBajDN5oMzzREn3-3WKCrjQIBe7xXVkb8ms_42HAxMiZdrLbCf1mLoJ_CMfAWJkTW0M2GmpXBJPFcdKwsmxahSmP3Ir8nRzrK76FRn1C8biVibs0MZ7cIIq-g2H--tsrml2IkL4-hZ1v5-NCF0Aq-WCcPNsHMdWTrBxpBSRkdEqiViulbfAEXJGSqkXez5VKNX_e2eKYwgu0uJN-BpD8Pqufi3H9M9UKjISkLdsXs5LdpaFN7at6BgMbW2Ce7RVTGsk3ir--rZzQg1oUnWVOKTNEwcTCIJGxm90Smse_aDcb3CBBwnMS_KbQ723ABKhW_m2q-mWeNgg";
 		cut = new XSUserInfoAdapter(new XsuaaToken(token));
 		assertThat(cut.getSubaccountId()).isEqualTo("test-subaccount");
 	}
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 9155bfa7..0d741cc8 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,14 +1,17 @@
 package com.sap.cloud.security.token;
 
 import com.sap.cloud.security.config.Service;
+import com.sap.cloud.security.json.DefaultJsonObject;
 import org.apache.commons.io.IOUtils;
 import org.junit.Test;
+import org.mockito.Mockito;
 
 import java.io.IOException;
 
 import static java.nio.charset.StandardCharsets.*;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.when;
 
 public class XsuaaTokenTest {
 
@@ -114,4 +117,23 @@ public class XsuaaTokenTest {
 		assertThat(userToken.getSubdomain()).isEqualTo("theSubdomain");
 	}
 
+	@Test
+	public void getSubaccountId() {
+		XsuaaToken userTokenWithSubaccountId = Mockito.spy(userToken);
+		when(userTokenWithSubaccountId.getClaimAsJsonObject(TokenClaims.XSUAA.EXTERNAL_ATTRIBUTE))
+				.thenReturn(new DefaultJsonObject("{\"subaccountid\": \"abc123\"}"));
+
+		assertThat(userTokenWithSubaccountId.getSubaccountId()).isEqualTo("abc123");
+	}
+
+	@Test
+	public void getSubaccountId_noSubaccountId_fallsBackToZoneId() {
+		assertThat(clientCredentialsToken.getSubaccountId()).isEqualTo("uaa");
+	}
+
+	@Test
+	public void getSubaccountId_noSubaccountIdAndNoFallback_isNull() {
+		assertThat(userToken.getSubaccountId()).isNull();
+	}
+
 }
\ No newline at end of file
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 75a99cda..d63bd67e 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
@@ -36,9 +36,7 @@ public class JwtValidatorBuilderTest {
 		OAuth2ServiceConfiguration configuration = xsuaaConfigBuilder.build();
 		JwtValidatorBuilder builder_1 = JwtValidatorBuilder.getInstance(configuration);
 		JwtValidatorBuilder builder_2 = JwtValidatorBuilder.getInstance(configuration);
-		JwtValidatorBuilder builder_3 = JwtValidatorBuilder.getInstance(xsuaaConfigBuilder.build());
 		assertThat(builder_1).isSameAs(builder_2);
-		assertThat(builder_1).isSameAs(builder_3);
 	}
 
 	@Test
diff --git a/spring-xsuaa/src/test/resources/saml.txt b/spring-xsuaa/src/test/resources/saml.txt
index 18e84ea2..bf6c20f8 100644
--- a/spring-xsuaa/src/test/resources/saml.txt
+++ b/spring-xsuaa/src/test/resources/saml.txt
@@ -13,8 +13,7 @@
     "xs.saml.groups": [
       "g1"
     ],
-    "xs.rolecollections": ["rc1"],
-    "subaccountid": "test-subaccount"
+    "xs.rolecollections": ["rc1"]
   },
   "sub": "1002191",
   "scope": [
@@ -47,6 +46,7 @@
   },
   "ext_attr": {
     "serviceinstanceid": "abcd1234",
-    "zdn": "testsubdomain"
+    "zdn": "testsubdomain",
+    "subaccountid": "test-subaccount"
   }
 }
\ No newline at end of file