Repository: SAP/cloud-security-xsuaa-integration
Pull request #607 (2021-07-30 17:05:39)

k8s support in java-security


Patch:
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 00000000..12bc2e2c
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,31 @@
+---
+name: Bug report
+about: Report a new bug
+title: ''
+labels: bug
+assignees: ''
+
+---
+
+Please first read carefully [Report an issue](https://github.com/SAP/cloud-security-xsuaa-integration/blob/master/CONTRIBUTING.md#report-an-issue) section before creating an Issue.
+
+**Issue Summary**
+- A clear and concise description of the issue.
+- Steps to reproduce the problem: 1. 2. 3.
+- What is the expected result?
+- What happened instead?
+- Additional information? (attach screenshots if applicable)
+
+<!-- Please make sure checklist is complete -->
+**Issue Report Checklist**
+
+- [ ] Real, current bug
+- [ ] Not a duplicate  
+- [ ] Not covered in "Common Pitfalls" section of corresponding module's README.md e.g. [java-security#common-pitfalls](https://github.com/SAP/cloud-security-xsuaa-integration/tree/master/java-security#common-pitfalls)
+- [ ] Reproducible
+- [ ] Good summary
+- [ ] Well-documented 
+    - [ ] log level increased to `DEBUG` debug logs provided
+    - [ ] POM provided
+    - [ ] dependency tree provided
+    - [ ] code snippet provided (when applicable)
diff --git a/.github/ISSUE_TEMPLATE/consultation-question.md b/.github/ISSUE_TEMPLATE/consultation-question.md
new file mode 100644
index 00000000..0e19a81e
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/consultation-question.md
@@ -0,0 +1,16 @@
+---
+name: Consultation question
+about: ask a question
+title: ''
+labels: consultation
+assignees: ''
+
+---
+
+Before creating a new consultation questions search first in [SAP Jam](https://jam4.sapjam.com/groups/DRuoC97ApSanbbXx20g4kb/forums) or [GitHub Consultation Questions](https://github.com/SAP/cloud-security-xsuaa-integration/issues?q=label%3Aconsultation+) for already replied questions. If you still don't find an answer create a new one.
+
+**Question Summary**
+If applicable, please mention:
+- client library (e.g. java-security, spring-xsuaa etc.) and version
+- Environment: Cloud Foundry or Kubernetes/Kyma
+- Additional information
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 00000000..f8dcf061
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,20 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: enhancement 
+assignees: ''
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 39123144..23c1bfab 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,21 @@
 # Change Log
 All notable changes to this project will be documented in this file.
 
+## 2.10.4
+- [java-security] Enrich `JsonParsingException` to detect wrong authorization headers earlier
+- [token-client] 
+  - `ClientCredentials`: solves incompatible change between 2.9.0 and 2.10.0
+  - `OAuth2TokenResponse.getTokenType()` exposes token type as provided by token request 
+- [spring-xsuaa] 
+  - `XsuaaServiceConfigurationDefault.hasProperty("apiurl")` returns true if VCAP_SERVICES-xsuaa-credentials contains attribute "apiurl"
+  -`XsuaaServiceConfigurationDefault.getProperty("apiurl")` returns value from VCAP_SERVICES-xsuaa-credentials-apiurl or null, if attribute does not exist.
+- [spring-security]`HybridJwtDecoder` raises ``BadJwtException`` in case the token is invalid and can not be decoded properly. 
+
+#### Dependency upgrades
+- wiremock 2.29.1 --> 2.30.1
+- io.projectreactor:reactor-core 3.4.8 --> 3.4.9  
+- io.projectreactor:reactor-test 3.4.8 --> 3.4.9
+
 ## 2.10.3
 #### Dependency upgrades
 - org.springframework.boot:spring-boot 2.5.0 --> 2.5.2
diff --git a/api/README.md b/api/README.md
index db1d1369..18996768 100644
--- a/api/README.md
+++ b/api/README.md
@@ -5,6 +5,6 @@
 <dependency>
     <groupId>com.sap.cloud.security.xsuaa</groupId>
     <artifactId>api</artifactId>
-    <version>2.10.3</version>
+    <version>2.10.4</version>
 </dependency>
 ```
diff --git a/api/pom.xml b/api/pom.xml
index 61a55558..3a9652b6 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -11,7 +11,7 @@
 	<parent>
 		<groupId>com.sap.cloud.security.xsuaa</groupId>
 		<artifactId>parent</artifactId>
-		<version>2.10.3</version>
+		<version>2.10.4</version>
 	</parent>
 
 	<packaging>jar</packaging>
diff --git a/java-api/README.md b/java-api/README.md
index 1776332f..01b71c48 100644
--- a/java-api/README.md
+++ b/java-api/README.md
@@ -5,6 +5,6 @@
 <dependency>
     <groupId>com.sap.cloud.security</groupId>
     <artifactId>java-api</artifactId>
-    <version>2.10.3</version>
+    <version>2.10.4</version>
 </dependency>
 ```
diff --git a/java-api/pom.xml b/java-api/pom.xml
index e002c702..9f998761 100644
--- a/java-api/pom.xml
+++ b/java-api/pom.xml
@@ -9,7 +9,7 @@
 	<parent>
 		<groupId>com.sap.cloud.security.xsuaa</groupId>
 		<artifactId>parent</artifactId>
-		<version>2.10.3</version>
+		<version>2.10.4</version>
 	</parent>
 
 	<groupId>com.sap.cloud.security</groupId>
diff --git a/java-api/src/main/java/com/sap/cloud/security/config/Environment.java b/java-api/src/main/java/com/sap/cloud/security/config/Environment.java
index 461a7ae6..0e1fb6b0 100644
--- a/java-api/src/main/java/com/sap/cloud/security/config/Environment.java
+++ b/java-api/src/main/java/com/sap/cloud/security/config/Environment.java
@@ -18,7 +18,7 @@ public interface Environment {
 	 * Represents a supported SAP CP environment.
 	 */
 	enum Type {
-		CF /* , KUBERNETES */;
+		CF, KUBERNETES;
 
 		public static Type from(String typeAsString) {
 			return Type.valueOf(typeAsString.toUpperCase());
diff --git a/java-api/src/main/java/com/sap/cloud/security/json/JsonParsingException.java b/java-api/src/main/java/com/sap/cloud/security/json/JsonParsingException.java
index d145f8e4..89ca6568 100644
--- a/java-api/src/main/java/com/sap/cloud/security/json/JsonParsingException.java
+++ b/java-api/src/main/java/com/sap/cloud/security/json/JsonParsingException.java
@@ -13,4 +13,8 @@ public class JsonParsingException extends RuntimeException {
 	public JsonParsingException(String message) {
 		super(message);
 	}
+
+	public JsonParsingException(String message, Throwable cause) {
+		super(message, cause);
+	}
 }
diff --git a/java-security-it/pom.xml b/java-security-it/pom.xml
index 06fe119a..e595ade0 100644
--- a/java-security-it/pom.xml
+++ b/java-security-it/pom.xml
@@ -9,7 +9,7 @@
     <parent>
         <artifactId>parent</artifactId>
         <groupId>com.sap.cloud.security.xsuaa</groupId>
-        <version>2.10.3</version>
+        <version>2.10.4</version>
     </parent>
 
     <artifactId>java-security-it</artifactId>
diff --git a/java-security/Migration_SpringSecurityProjects.md b/java-security/Migration_SpringSecurityProjects.md
index 55d0c978..b76c77b1 100644
--- a/java-security/Migration_SpringSecurityProjects.md
+++ b/java-security/Migration_SpringSecurityProjects.md
@@ -37,19 +37,19 @@ First make sure you have the following dependencies defined in your pom.xml:
 <dependency>
   <groupId>com.sap.cloud.security.xsuaa</groupId>
   <artifactId>api</artifactId>
-  <version>2.10.3</version>
+  <version>2.10.4</version>
 </dependency>
 <dependency>
   <groupId>com.sap.cloud.security</groupId>
   <artifactId>java-security</artifactId>
-  <version>2.10.3</version>
+  <version>2.10.4</version>
 </dependency>
 
 <!-- new java-security dependencies for unit tests -->
 <dependency>
   <groupId>com.sap.cloud.security</groupId>
   <artifactId>java-security-test</artifactId>
-  <version>2.10.3</version>
+  <version>2.10.4</version>
   <scope>test</scope>
 </dependency>
 ```
diff --git a/java-security/README.md b/java-security/README.md
index b3390b6e..91699f8f 100644
--- a/java-security/README.md
+++ b/java-security/README.md
@@ -2,7 +2,7 @@
 
 Token Validation for Java applications.
 
-- Loads Identity Service Configuration from `VCAP_SERVICES` environment. The [`Environments`](src/main/java/com/sap/cloud/security/config/Environments.java) serves as central entry point to get or parse the  [`OAuth2ServiceConfiguration`](src/main/java/com/sap/cloud/security/config/OAuth2ServiceConfiguration.java) within SAP Cloud Platform.
+- Loads Identity Service Configuration from `VCAP_SERVICES` in Cloud Foundry or from secrets in Kubernetes environment. The [`Environments`](src/main/java/com/sap/cloud/security/config/Environments.java) serves as central entry point to get or parse the  [`OAuth2ServiceConfiguration`](src/main/java/com/sap/cloud/security/config/OAuth2ServiceConfiguration.java) within SAP Business Technology Platform.
 - Decodes and parses encoded JSON Web Tokens ([`Token`](/java-api/src/main/java/com/sap/cloud/security/token/Token.java)) and provides convenient access to token header parameters and claims. A Java implementation of JSON Web Token (JWT) - [RFC 7519](https://tools.ietf.org/html/rfc7519). 
 - Validates the decoded token. The [`JwtValidatorBuilder`](src/main/java/com/sap/cloud/security/token/validation/validators/JwtValidatorBuilder.java) comprises the following mandatory checks:
   - Is the JWT used before the `exp` (expiration) time and eventually is it used after the `nbf` (not before) time ([`JwtTimestampValidator`](
@@ -26,7 +26,7 @@ In case of XSUAA does the JWT provide a valid `jku` token header parameter that
 
 ## Supported Environments
 - Cloud Foundry
-- Planned: Kubernetes
+- Kubernetes/Kyma
 
 ## Supported Identity Services
 - XSUAA
@@ -47,7 +47,7 @@ In case of XSUAA does the JWT provide a valid `jku` token header parameter that
 <dependency>
     <groupId>com.sap.cloud.security</groupId>
     <artifactId>java-security</artifactId>
-    <version>2.10.3</version>
+    <version>2.10.4</version>
 </dependency>
 <dependency>
     <groupId>org.apache.httpcomponents</groupId>
@@ -77,7 +77,7 @@ logger options.
 ```java
 OAuth2ServiceConfiguration serviceConfig = Environments.getCurrent().getXsuaaConfiguration();
 ```
-> Note: By default `Environments` auto-detects the environment: Cloud Foundry or Kubernetes.
+> Note: By default `Environments` auto-detects the environment: Cloud Foundry or Kubernetes. 
 
 Alternatively you can also specify the Service Configuration by your own:
 ```java
@@ -89,7 +89,21 @@ OAuth2ServiceConfiguration serviceConfig = OAuth2ServiceConfigurationBuilder.for
       .withClientSecret("oauth-client-secret")
       .build();
 ```
-:bulb: `OAuth2ServiceConfiguration.getClientIdentity()` is a convenience method that with `OAuth2ServiceConfigurationBuilder` implementation will resolve `ClientCredential` or `ClientCertificate` implementations of `ClientIdentity` interface based on the `credential-type` from the Xsuaa service binding. Therefore, providing the correct implementation for the configured authentication type e.g. X.509 or client secret based
+:bulb: `OAuth2ServiceConfiguration.getClientIdentity()` is a convenience method that with `OAuth2ServiceConfigurationBuilder` implementation will resolve `ClientCredentials` or `ClientCertificate` implementations of `ClientIdentity` interface based on the `credential-type` from the Xsuaa service binding. Therefore, providing the correct implementation for the configured authentication type e.g. X.509 or client secret based.
+
+#### :mega: Service configuration in Kubernetes environment 
+To access service instance configurations from the application, Kubernetes secrets need to be provided as files in a volume mounted on application's container. 
+By default, library will search the configuration files in the following paths:
+- XSUAA: `/etc/secrets/sapcp/xsuaa/<YOUR XSUAA INSTANCE NAME>`
+- IAS: `/etc/secrets/sapcp/ias/<YOUR IAS INSTANCE NAME>`
+- Service-manager: `/etc/secrets/sapcp/service-manager/<YOUR SERVICE-MANAGER NAME>`
+
+:exclamation: service-manager binding is mandatory!
+
+It's possible to provide custom paths for Xsuaa and IAS:
+```java
+K8sEnvironment.getInstance(customXsuaaPath, customIasPath);
+```
 
 ### Setup Step 2: Setup Validators
 Now configure the `JwtValidatorBuilder` once with the service configuration from the previous step.
diff --git a/java-security/pom.xml b/java-security/pom.xml
index af44b7e6..7faf5761 100644
--- a/java-security/pom.xml
+++ b/java-security/pom.xml
@@ -9,7 +9,7 @@
 	<parent>
 		<groupId>com.sap.cloud.security.xsuaa</groupId>
 		<artifactId>parent</artifactId>
-		<version>2.10.3</version>
+		<version>2.10.4</version>
 	</parent>
 
 	<groupId>com.sap.cloud.security</groupId>
@@ -133,6 +133,12 @@
 			<version>${spring.security.version}</version>
 			<scope>test</scope>
 		</dependency>
+        <dependency>
+            <groupId>uk.org.webcompere</groupId>
+            <artifactId>system-stubs-jupiter</artifactId>
+            <version>1.2.0</version>
+            <scope>test</scope>
+        </dependency>
 
 	</dependencies>
 
diff --git a/java-security/src/main/java/com/sap/cloud/security/config/Environments.java b/java-security/src/main/java/com/sap/cloud/security/config/Environments.java
index f176e69c..3098550d 100644
--- a/java-security/src/main/java/com/sap/cloud/security/config/Environments.java
+++ b/java-security/src/main/java/com/sap/cloud/security/config/Environments.java
@@ -6,6 +6,9 @@
 package com.sap.cloud.security.config;
 
 import com.sap.cloud.security.config.cf.CFEnvironment;
+import com.sap.cloud.security.config.k8s.K8sEnvironment;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.InputStream;
 import java.util.Scanner;
@@ -16,7 +19,11 @@ import java.util.Scanner;
  */
 public class Environments {
 
-	private static final Environment cfEnvironment = CFEnvironment.getInstance(); // singleton
+	private static final Logger LOGGER = LoggerFactory.getLogger(Environments.class);
+
+	private static Environment cfEnvironment; // singleton
+	private static Environment k8sEnvironment;  // singleton
+	private static Boolean isK8sEnv;
 
 	private Environments() {
 		// use factoryMethods instead
@@ -28,13 +35,32 @@ public class Environments {
 	 * @return the current environment
 	 */
 	public static Environment getCurrent() {
-		// TODO Kubernetes: probe in which environment it runs currently: CF or
+		if (isK8sEnv()){
+			LOGGER.debug("K8s environment detected");
+			return getK8sEnvironment();
+		} else {
+			LOGGER.debug("CF environment detected");
+			return getCfEnvironment();
+		}
+	}
+
+	private static Environment getCfEnvironment() {
+		if (cfEnvironment == null) {
+			cfEnvironment = CFEnvironment.getInstance();
+		}
 		return cfEnvironment;
 	}
 
+	private static Environment getK8sEnvironment() {
+		if (k8sEnvironment == null) {
+			k8sEnvironment = K8sEnvironment.getInstance();
+		}
+		return k8sEnvironment;
+	}
+
 	/**
 	 * Reads {@link Environment} not from system environment but from
-	 * {@link InputStream}.
+	 * {@link InputStream}. Is applicable only to CF environment and expects the input to be in VCAP services format.
 	 * 
 	 * @param input
 	 *            e.g. from file
@@ -49,4 +75,11 @@ public class Environments {
 		return CFEnvironment.getInstance(str -> vcapServices.toString(), str -> null);
 	}
 
+	private static boolean isK8sEnv() {
+		if (isK8sEnv == null){
+			isK8sEnv = System.getenv().get("KUBERNETES_SERVICE_HOST") != null;
+		}
+		return isK8sEnv;
+	}
+
 }
diff --git a/java-security/src/main/java/com/sap/cloud/security/config/k8s/K8sEnvironment.java b/java-security/src/main/java/com/sap/cloud/security/config/k8s/K8sEnvironment.java
new file mode 100644
index 00000000..ab63fc7f
--- /dev/null
+++ b/java-security/src/main/java/com/sap/cloud/security/config/k8s/K8sEnvironment.java
@@ -0,0 +1,178 @@
+package com.sap.cloud.security.config.k8s;
+
+import com.sap.cloud.security.config.Environment;
+import com.sap.cloud.security.config.OAuth2ServiceConfiguration;
+import com.sap.cloud.security.config.OAuth2ServiceConfigurationBuilder;
+import com.sap.cloud.security.config.Service;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.*;
+import java.util.function.UnaryOperator;
+import java.util.stream.Collectors;
+
+import static com.sap.cloud.security.config.Service.IAS;
+import static com.sap.cloud.security.config.Service.XSUAA;
+
+/**
+ * Loads the OAuth configuration ({@link OAuth2ServiceConfiguration}) of a
+ * supported identity {@link Service} in the Kubernetes Environment by
+ * accessing defaults service secrets paths "/etc/secrets/sapcp/xsuaa" for Xsuaa service or "/etc/secrets/sapcp/ias" for .
+ * IAS service
+ */
+public class K8sEnvironment implements Environment {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(K8sEnvironment.class);
+
+    private static K8sEnvironment instance;
+    private static final String DEFAULT_XSUAA_PATH = "/etc/secrets/sapcp/xsuaa";
+    private static final String DEFAULT_IAS_PATH = "/etc/secrets/sapcp/ias";
+    private static String customXsuaaPath;
+    private static String customIasPath;
+
+    private Map<Service, List<OAuth2ServiceConfiguration>> serviceConfigurations;
+    private UnaryOperator<String> systemEnvironmentProvider;
+    private UnaryOperator<String> systemPropertiesProvider;
+
+    public static K8sEnvironment getInstance() {
+        return getInstance(System::getenv, System::getProperty);
+    }
+
+    public static K8sEnvironment getInstance(UnaryOperator<String> systemEnvironmentProvider,
+                                            UnaryOperator<String> systemPropertiesProvider) {
+        if (instance == null) {
+            instance = new K8sEnvironment();
+        }
+        instance.systemEnvironmentProvider = systemEnvironmentProvider;
+        instance.systemPropertiesProvider = systemPropertiesProvider;
+        instance.serviceConfigurations = loadAll();
+        return instance;
+    }
+
+    public static K8sEnvironment getInstance(@Nullable String customXsuaaPath, @Nullable String customIasPath) {
+        K8sEnvironment.customXsuaaPath = customXsuaaPath;
+        K8sEnvironment.customIasPath = customIasPath;
+        return getInstance(System::getenv, System::getProperty);
+    }
+
+    private static Map<Service, List<OAuth2ServiceConfiguration>> loadAll() {
+        Map<Service, List<OAuth2ServiceConfiguration>> serviceConfigurations = new HashMap<>(); // NOSONAR
+        List<OAuth2ServiceConfiguration> allXsuaaServices = loadOauth2ServiceConfig(Service.XSUAA);
+        List<OAuth2ServiceConfiguration> iasService = loadOauth2ServiceConfig(Service.IAS);
+
+        serviceConfigurations.put(Service.XSUAA, allXsuaaServices);
+        serviceConfigurations.put(Service.IAS, iasService);
+        return serviceConfigurations;
+    }
+
+    private static List<OAuth2ServiceConfiguration> loadOauth2ServiceConfig(Service service) {
+        List<OAuth2ServiceConfiguration> allServices = new ArrayList<>();
+        File[] serviceBindings = getServiceBindings(service);
+        if (serviceBindings != null){
+            for (File binding : serviceBindings){
+                Map<String, String> servicePropertiesMap = getServiceProperties(binding);
+                OAuth2ServiceConfiguration config = OAuth2ServiceConfigurationBuilder.forService(service)
+                        .withProperties(servicePropertiesMap)
+                        .build();
+                allServices.add(config);
+            }
+        } else {
+            LOGGER.debug("No service bindings for {} service was found.", service);
+        }
+        return allServices;
+    }
+
+    @Nonnull
+    @Override
+    public Type getType() {
+        return Type.KUBERNETES;
+    }
+
+    @Nonnull
+    @Override
+    public OAuth2ServiceConfiguration getXsuaaConfiguration() {
+        return serviceConfigurations.get(XSUAA).get(0);
+    }
+
+    @Nonnull
+    @Override
+    public OAuth2ServiceConfiguration getIasConfiguration() {
+        return serviceConfigurations.get(IAS).get(0);
+    }
+
+    @Override
+    public int getNumberOfXsuaaConfigurations() {
+        return getServiceBindings(XSUAA) != null ? getServiceBindings(XSUAA).length : 0;
+    }
+
+    @Nonnull
+    @Override
+    public OAuth2ServiceConfiguration getXsuaaConfigurationForTokenExchange() {
+        //TODO should load broker plan
+        return getXsuaaConfiguration();
+    }
+
+    @Nullable
+    private static File[] getServiceBindings(Service service) {
+        if (service == Service.XSUAA){
+            if (customXsuaaPath != null) {
+                LOGGER.debug("Retrieving Xsuaa service bindings from {}", customXsuaaPath);
+                return new File(customXsuaaPath).listFiles();
+            }
+            LOGGER.debug("Retrieving Xsuaa service bindings from {}", DEFAULT_XSUAA_PATH);
+            return new File(DEFAULT_XSUAA_PATH).listFiles();
+        } else {
+            if (customIasPath != null) {
+                LOGGER.debug("Retrieving IAS service bindings from {}", customIasPath);
+                return new File(customXsuaaPath).listFiles();
+            }
+            LOGGER.debug("Retrieving IAS service bindings from {}", DEFAULT_IAS_PATH);
+            return new File(DEFAULT_IAS_PATH).listFiles();
+        }
+    }
+
+
+    private static List<File> getBindingFiles(@Nonnull File binding) {
+        File [] bindingFiles = new File(binding.getPath()).listFiles();
+        if (bindingFiles == null || bindingFiles.length == 0) {
+            LOGGER.warn("No service binding files were found for {}", binding.getName());
+            return Collections.emptyList();
+        }
+        return Arrays.stream(bindingFiles).filter(File::isFile)
+                .collect(Collectors.toList());
+    }
+
+    private static Map<String, String> getServiceProperties(File binding) {
+        List<File> serviceBindingFiles = getBindingFiles(binding);
+        if (serviceBindingFiles.isEmpty()) {
+            return Collections.emptyMap();
+        }
+        return mapServiceProperties(serviceBindingFiles);
+    }
+
+    private static Map<String, String> mapServiceProperties(List<File> servicePropertiesList) {
+        final Map<String, String> serviceProperties = new HashMap<>();
+        for (final File property : servicePropertiesList) {
+            try {
+                final List<String> lines = readLinesFromFile(property);
+                serviceProperties.put(property.getName(), String.join("\\n", lines));
+            } catch (IOException ex) {
+                LOGGER.error("Failed to read content of service configuration property files", ex);
+                return serviceProperties;
+            }
+        }
+        LOGGER.debug("K8s secrets for {} service: {}", servicePropertiesList.get(0).getParent(), serviceProperties);
+        return serviceProperties;
+    }
+
+    @Nonnull
+    private static List<String> readLinesFromFile(File property) throws IOException {
+        return Files.readAllLines(Paths.get(property.getAbsolutePath()));
+    }
+}
diff --git a/java-security/src/main/java/com/sap/cloud/security/servlet/HybridTokenFactory.java b/java-security/src/main/java/com/sap/cloud/security/servlet/HybridTokenFactory.java
index d25a45be..960d54e4 100644
--- a/java-security/src/main/java/com/sap/cloud/security/servlet/HybridTokenFactory.java
+++ b/java-security/src/main/java/com/sap/cloud/security/servlet/HybridTokenFactory.java
@@ -8,6 +8,7 @@ package com.sap.cloud.security.servlet;
 import com.sap.cloud.security.config.Environments;
 import com.sap.cloud.security.config.OAuth2ServiceConfiguration;
 import com.sap.cloud.security.config.cf.CFConstants;
+import com.sap.cloud.security.json.JsonParsingException;
 import com.sap.cloud.security.token.*;
 import com.sap.cloud.security.xsuaa.Assertions;
 import com.sap.cloud.security.xsuaa.jwt.Base64JwtDecoder;
@@ -43,14 +44,17 @@ public class HybridTokenFactory implements TokenFactory {
 	 * @return the new token instance
 	 */
 	public Token create(String jwtToken) {
+		try {
+			Objects.requireNonNull(jwtToken, "Requires encoded jwtToken to create a Token instance.");
+			DecodedJwt decodedJwt = Base64JwtDecoder.getInstance().decode(removeBearer(jwtToken));
 
-		Objects.requireNonNull(jwtToken, "Requires encoded jwtToken to create a Token instance.");
-		DecodedJwt decodedJwt = Base64JwtDecoder.getInstance().decode(removeBearer(jwtToken));
-
-		if (isXsuaaToken(decodedJwt)) {
-			return new XsuaaToken(decodedJwt).withScopeConverter(getOrCreateScopeConverter());
+			if (isXsuaaToken(decodedJwt)) {
+				return new XsuaaToken(decodedJwt).withScopeConverter(getOrCreateScopeConverter());
+			}
+			return new SapIdToken(decodedJwt);
+		} catch (JsonParsingException e) {
+			throw new JsonParsingException(String.format("Issue with Jwt parsing. Authorization header: %s - %s", jwtToken.substring(0, 20), e.getMessage()), e);
 		}
-		return new SapIdToken(decodedJwt);
 	}
 
 	/**
@@ -76,7 +80,7 @@ public class HybridTokenFactory implements TokenFactory {
 		if (xsAppId == null) {
 			OAuth2ServiceConfiguration serviceConfiguration = Environments.getCurrent().getXsuaaConfiguration();
 			if (serviceConfiguration == null) {
-				LOGGER.error("There is no xsuaa service configuration: no local scope check possible.");
+				LOGGER.warn("There is no xsuaa service configuration: no local scope check possible.");
 			} else {
 				xsAppId = serviceConfiguration.getProperty(CFConstants.XSUAA.APP_ID);
 			}
diff --git a/pom.xml b/pom.xml
index fa65d2bb..cd1230aa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
 
 	<groupId>com.sap.cloud.security.xsuaa</groupId>
 	<artifactId>parent</artifactId>
-	<version>2.10.3</version>
+	<version>2.10.4</version>
 	<packaging>pom</packaging>
 
 	<name>parent</name>
@@ -63,7 +63,7 @@
 		<spring.security.version>5.5.1</spring.security.version>
 		<spring.security.oauth2.version>2.5.1.RELEASE</spring.security.oauth2.version>
 		<spring.security.jwt.version>1.1.1.RELEASE</spring.security.jwt.version>
-		<reactor.version>3.4.8</reactor.version>
+		<reactor.version>3.4.9</reactor.version>
 		<log4j.version>2.14.1</log4j.version>
 		<slf4j.api.version>1.7.32</slf4j.api.version> <!--see also here http://www.slf4j.org/faq.html#changesInVersion18 -->
 		<org.json.version>20210307</org.json.version>
@@ -79,7 +79,7 @@
 		<hamcrest.version>1.3</hamcrest.version>
 		<mockito.version>3.11.2</mockito.version>
 		<assertj.version>3.20.2</assertj.version>
-		<reactor.test.version>3.4.8</reactor.test.version>
+		<reactor.test.version>3.4.9</reactor.test.version>
 		<skipTests>false</skipTests>
 		<jacoco.skip>${skipTests}</jacoco.skip>
 	</properties>
diff --git a/samples/java-security-usage-ias/pom.xml b/samples/java-security-usage-ias/pom.xml
index e88c2ca4..4ef6258a 100755
--- a/samples/java-security-usage-ias/pom.xml
+++ b/samples/java-security-usage-ias/pom.xml
@@ -6,7 +6,7 @@
     <modelVersion>4.0.0</modelVersion>
     <groupId>com.sap.cloud.security.xssec.samples</groupId>
     <artifactId>java-security-usage-ias</artifactId>
-    <version>2.10.3</version>
+    <version>2.10.4</version>
     <packaging>war</packaging>
 
     <!--profiles>
@@ -27,7 +27,7 @@
     <properties>
         <maven.compiler.source>1.8</maven.compiler.source>
         <maven.compiler.target>1.8</maven.compiler.target>
-        <sap.cloud.security.version>2.10.3</sap.cloud.security.version>
+        <sap.cloud.security.version>2.10.4</sap.cloud.security.version>
         <slf4j.api.version>1.7.30</slf4j.api.version>
         <apache.httpclient.version>4.5.13</apache.httpclient.version>
         <javax.servlet.api.version>4.0.1</javax.servlet.api.version>
diff --git a/samples/java-security-usage/Dockerfile b/samples/java-security-usage/Dockerfile
new file mode 100644
index 00000000..9f1e7157
--- /dev/null
+++ b/samples/java-security-usage/Dockerfile
@@ -0,0 +1,5 @@
+FROM tomcat:8-jre8-slim
+
+ADD target/java-security-usage.war /usr/local/tomcat/webapps/
+
+EXPOSE 8080
\ No newline at end of file
diff --git a/samples/java-security-usage/k8s/deployment.yml b/samples/java-security-usage/k8s/deployment.yml
new file mode 100644
index 00000000..2fd68fb2
--- /dev/null
+++ b/samples/java-security-usage/k8s/deployment.yml
@@ -0,0 +1,64 @@
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  creationTimestamp: null
+  labels:
+    app: java-security-usage
+    kyma-grafana: enabled
+  name: java-security-usage
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: java-security-usage
+  strategy: {}
+  template:
+    metadata:
+      creationTimestamp: null
+      labels:
+        app: java-security-usage
+        kyma-grafana: enabled
+    spec:
+      containers:
+        - image: <placeholder for image repository>
+          imagePullPolicy: Always
+          name: java-security-usage
+          ports:
+            - name: http
+              containerPort: 8080
+          volumeMounts:
+            - name: xsuaa
+              mountPath: "/etc/secrets/sapcp/xsuaa/xsuaa-instance"
+              readOnly: true
+            - name: xsuaa2
+              mountPath: "/etc/secrets/sapcp/xsuaa/xsuaa-instance-1"
+              readOnly: true
+
+          envFrom:
+            - secretRef:
+                name: java-security-usage
+                optional: true
+      volumes:
+        - name: xsuaa
+          secret:
+            secretName: <placeholder for secret name>
+        - name: xsuaa2
+          secret:
+            secretName: <placeholder for secret name>
+---
+apiVersion: v1
+kind: Service
+metadata:
+  creationTimestamp: null
+  labels:
+    app: java-security-usage
+  name: java-security-usage
+spec:
+  ports:
+    - name: http
+      port: 80
+      protocol: TCP
+      targetPort: 8080
+  selector:
+    app: java-security-usage
\ No newline at end of file
diff --git a/samples/java-security-usage/pom.xml b/samples/java-security-usage/pom.xml
index f46e55c3..f4b7b09c 100755
--- a/samples/java-security-usage/pom.xml
+++ b/samples/java-security-usage/pom.xml
@@ -6,7 +6,7 @@
     <modelVersion>4.0.0</modelVersion>
     <groupId>com.sap.cloud.security.xssec.samples</groupId>
     <artifactId>java-security-usage</artifactId>
-    <version>2.10.3</version>
+    <version>2.10.4</version>
     <packaging>war</packaging>
 
     <!--profiles>
@@ -27,7 +27,7 @@
     <properties>
         <maven.compiler.source>1.8</maven.compiler.source>
         <maven.compiler.target>1.8</maven.compiler.target>
-        <sap.cloud.security.version>2.10.3</sap.cloud.security.version>
+        <sap.cloud.security.version>2.10.4</sap.cloud.security.version>
         <slf4j.api.version>1.7.30</slf4j.api.version>
         <apache.httpclient.version>4.5.13</apache.httpclient.version>
         <javax.servlet.api.version>4.0.1</javax.servlet.api.version>
diff --git a/samples/java-tokenclient-usage/pom.xml b/samples/java-tokenclient-usage/pom.xml
index 9adced79..39feefbd 100755
--- a/samples/java-tokenclient-usage/pom.xml
+++ b/samples/java-tokenclient-usage/pom.xml
@@ -6,13 +6,13 @@
     <modelVersion>4.0.0</modelVersion>
     <groupId>com.sap.cloud.security.xssec.samples</groupId>
     <artifactId>java-tokenclient-usage</artifactId>
-    <version>2.10.3</version>
+    <version>2.10.4</version>
     <packaging>war</packaging>
 
     <properties>
         <maven.compiler.source>1.8</maven.compiler.source>
         <maven.compiler.target>1.8</maven.compiler.target>
-        <sap.cloud.security.version>2.10.3</sap.cloud.security.version>
+        <sap.cloud.security.version>2.10.4</sap.cloud.security.version>
         <apache.httpclient.version>4.5.13</apache.httpclient.version>
         <javax.servlet.api.version>4.0.1</javax.servlet.api.version>
         <slf4j.api.version>1.7.30</slf4j.api.version>
diff --git a/samples/sap-java-buildpack-api-usage/pom.xml b/samples/sap-java-buildpack-api-usage/pom.xml
index b2d122c6..2d836834 100755
--- a/samples/sap-java-buildpack-api-usage/pom.xml
+++ b/samples/sap-java-buildpack-api-usage/pom.xml
@@ -6,7 +6,7 @@
 	<modelVersion>4.0.0</modelVersion>
 	<groupId>com.sap.cloud.security.xssec.samples</groupId>
 	<artifactId>sap-java-buildpack-api-usage</artifactId>
-	<version>2.10.3</version>
+	<version>2.10.4</version>
 	<packaging>war</packaging>
 
 	<properties>
@@ -28,17 +28,17 @@
 			<scope>provided</scope>
 		</dependency>
 	</dependencies>
-    <dependencyManagement>
-        <dependencies>
-            <dependency>
-                <groupId>com.sap.cloud.sjb.cf</groupId>
-                <artifactId>sap-java-buildpack-bom</artifactId>
-                <version>1.31.1</version>
-                <type>pom</type>
-                <scope>import</scope>
-            </dependency>
-        </dependencies>
-    </dependencyManagement>
+	<dependencyManagement>
+		<dependencies>
+			<dependency>
+				<groupId>com.sap.cloud.sjb.cf</groupId>
+				<artifactId>sap-java-buildpack-bom</artifactId>
+				<version>1.38.0</version>
+				<type>pom</type>
+				<scope>import</scope>
+			</dependency>
+		</dependencies>
+	</dependencyManagement>
 	<build>
 		<plugins>
 			<plugin>
diff --git a/samples/spring-security-basic-auth/pom.xml b/samples/spring-security-basic-auth/pom.xml
index 0cc34936..2fbbe8cc 100644
--- a/samples/spring-security-basic-auth/pom.xml
+++ b/samples/spring-security-basic-auth/pom.xml
@@ -13,14 +13,14 @@
 	</parent>
 
 	<artifactId>spring-security-basic-auth</artifactId>
-	<version>2.10.3</version>
+	<version>2.10.4</version>
 	<name>spring-security-basic-auth</name>
 
 	<properties>
 		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 		<java.version>1.8</java.version>
-		<sap.cloud.security.version>2.10.3</sap.cloud.security.version>
+		<sap.cloud.security.version>2.10.4</sap.cloud.security.version>
 		<apache.httpclient.version>4.5.13</apache.httpclient.version>
 	</properties>
 
diff --git a/samples/spring-security-hybrid-usage/pom.xml b/samples/spring-security-hybrid-usage/pom.xml
index 77695a8b..c5cd12c5 100644
--- a/samples/spring-security-hybrid-usage/pom.xml
+++ b/samples/spring-security-hybrid-usage/pom.xml
@@ -16,7 +16,7 @@
 
 	<groupId>com.sap.cloud.security.samples</groupId>
 	<artifactId>spring-security-hybrid-usage</artifactId>
-	<version>2.10.3</version>
+	<version>2.10.4</version>
 
 	<properties>
 		<!-- 
@@ -28,7 +28,7 @@
 		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 		<java.version>1.8</java.version>
-		<sap.cloud.security.version>2.10.3</sap.cloud.security.version>
+		<sap.cloud.security.version>2.10.4</sap.cloud.security.version>
 		<apache.httpclient.version>4.5.13</apache.httpclient.version>
 	</properties>
 
diff --git a/samples/spring-security-xsuaa-usage/pom.xml b/samples/spring-security-xsuaa-usage/pom.xml
index 68974d51..7ed159f3 100644
--- a/samples/spring-security-xsuaa-usage/pom.xml
+++ b/samples/spring-security-xsuaa-usage/pom.xml
@@ -16,7 +16,7 @@
 
 	<groupId>com.sap.cloud.security.samples</groupId>
 	<artifactId>spring-security-xsuaa-usage</artifactId>
-	<version>2.10.3</version>
+	<version>2.10.4</version>
 	<name>spring-security-xsuaa-usage</name>
 
 	<properties>
@@ -29,7 +29,7 @@
 		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 		<java.version>1.8</java.version>
-		<sap.cloud.security.version>2.10.3</sap.cloud.security.version>
+		<sap.cloud.security.version>2.10.4</sap.cloud.security.version>
 		<apache.httpclient.version>4.5.13</apache.httpclient.version>
 	</properties>
 
diff --git a/samples/spring-webflux-security-xsuaa-usage/pom.xml b/samples/spring-webflux-security-xsuaa-usage/pom.xml
index 9cf93bd5..34d5b532 100644
--- a/samples/spring-webflux-security-xsuaa-usage/pom.xml
+++ b/samples/spring-webflux-security-xsuaa-usage/pom.xml
@@ -14,12 +14,12 @@
 
     <groupId>com.sap.cloud.security.samples</groupId>
     <artifactId>spring-webflux-security-xsuaa-usage</artifactId>
-    <version>2.10.3</version>
+    <version>2.10.4</version>
     <name>spring-webflux-security-xsuaa-usage</name>
 
     <properties>
         <java.version>1.8</java.version>
-        <sap.cloud.security.version>2.10.3</sap.cloud.security.version>
+        <sap.cloud.security.version>2.10.4</sap.cloud.security.version>
     </properties>
 
     <dependencies>
diff --git a/spring-security-starter/pom.xml b/spring-security-starter/pom.xml
index 9430173e..eca8598b 100644
--- a/spring-security-starter/pom.xml
+++ b/spring-security-starter/pom.xml
@@ -16,7 +16,7 @@
 	<parent>
 		<groupId>com.sap.cloud.security.xsuaa</groupId>
 		<artifactId>parent</artifactId>
-		<version>2.10.3</version>
+		<version>2.10.4</version>
 	</parent>
 
 	<groupId>com.sap.cloud.security</groupId>
diff --git a/spring-security/README.md b/spring-security/README.md
index c5fc39d4..f1da1370 100644
--- a/spring-security/README.md
+++ b/spring-security/README.md
@@ -34,7 +34,7 @@ These (spring) dependencies needs to be provided:
 <dependency>
     <groupId>com.sap.cloud.security</groupId>
     <artifactId>resourceserver-security-spring-boot-starter</artifactId>
-    <version>2.10.3</version>
+    <version>2.10.4</version>
 </dependency>
 <dependency>
     <groupId>org.apache.httpcomponents</groupId>
diff --git a/spring-security/pom.xml b/spring-security/pom.xml
index 4856d4b2..7da61c49 100644
--- a/spring-security/pom.xml
+++ b/spring-security/pom.xml
@@ -9,13 +9,13 @@
 	<parent>
 		<groupId>com.sap.cloud.security.xsuaa</groupId>
 		<artifactId>parent</artifactId>
-		<version>2.10.3</version>
+		<version>2.10.4</version>
 	</parent>
 
 	<groupId>com.sap.cloud.security</groupId>
 	<artifactId>spring-security</artifactId>
 	<packaging>jar</packaging>
-	<version>2.10.3</version>
+	<version>2.10.4</version>
 
 	<dependencies>
 		<dependency>
diff --git a/spring-security/src/main/java/com/sap/cloud/security/spring/token/authentication/HybridJwtDecoder.java b/spring-security/src/main/java/com/sap/cloud/security/spring/token/authentication/HybridJwtDecoder.java
index 9c88d603..3cbcb53a 100644
--- a/spring-security/src/main/java/com/sap/cloud/security/spring/token/authentication/HybridJwtDecoder.java
+++ b/spring-security/src/main/java/com/sap/cloud/security/spring/token/authentication/HybridJwtDecoder.java
@@ -46,10 +46,16 @@ public class HybridJwtDecoder implements JwtDecoder {
 
 	@Override
 	public Jwt decode(String encodedToken) {
-		Assert.hasText(encodedToken, "encodedToken must neither be null nor empty String.");
-		Token token = Token.create(encodedToken);
+		Token token;
+		Jwt jwt;
+		try {
+			Assert.hasText(encodedToken, "encodedToken must neither be null nor empty String.");
+			token = Token.create(encodedToken);
+			jwt = parseJwt(token);
+		} catch (RuntimeException ex) {
+			throw new BadJwtException("Error initializing JWT decoder: " + ex.getMessage(), ex);
+		}
 		ValidationResult validationResult;
-
 		switch (token.getService()) {
 		case IAS:
 			validationResult = iasTokenValidators.validate(token);
@@ -64,7 +70,7 @@ public class HybridJwtDecoder implements JwtDecoder {
 			throw new BadJwtException("The token is invalid: " + validationResult.getErrorDescription());
 		}
 		logger.debug("The token of service {} was successfully validated.", token.getService());
-		return parseJwt(token);
+		return jwt;
 	}
 
 	/**
diff --git a/spring-xsuaa-it/pom.xml b/spring-xsuaa-it/pom.xml
index b03438fa..03501870 100644
--- a/spring-xsuaa-it/pom.xml
+++ b/spring-xsuaa-it/pom.xml
@@ -8,7 +8,7 @@
 	<parent>
 		<groupId>com.sap.cloud.security.xsuaa</groupId>
 		<artifactId>parent</artifactId>
-		<version>2.10.3</version>
+		<version>2.10.4</version>
 	</parent>
 
 	<artifactId>spring-xsuaa-it</artifactId>
diff --git a/spring-xsuaa-mock/README.md b/spring-xsuaa-mock/README.md
index 5b79de9a..acbdf52c 100644
--- a/spring-xsuaa-mock/README.md
+++ b/spring-xsuaa-mock/README.md
@@ -21,7 +21,7 @@ The default implementation offers already valid *token_keys* for JWT tokens, tha
 <dependency>
     <groupId>com.sap.cloud.security.xsuaa</groupId>
     <artifactId>spring-xsuaa-mock</artifactId>
-    <version>2.10.3</version>
+    <version>2.10.4</version>
 </dependency>
 <dependency> <!-- new with version 1.5.0 - provided with org.springframework.boot:spring-boot-starter:jar -->
     <groupId>org.springframework.boot</groupId>
diff --git a/spring-xsuaa-mock/pom.xml b/spring-xsuaa-mock/pom.xml
index 373f6713..3678f1be 100644
--- a/spring-xsuaa-mock/pom.xml
+++ b/spring-xsuaa-mock/pom.xml
@@ -9,7 +9,7 @@
 	<parent>
 		<groupId>com.sap.cloud.security.xsuaa</groupId>
 		<artifactId>parent</artifactId>
-		<version>2.10.3</version>
+		<version>2.10.4</version>
 	</parent>
 
 	<artifactId>spring-xsuaa-mock</artifactId>
diff --git a/spring-xsuaa-starter/pom.xml b/spring-xsuaa-starter/pom.xml
index 6ac6363b..237f94fe 100644
--- a/spring-xsuaa-starter/pom.xml
+++ b/spring-xsuaa-starter/pom.xml
@@ -16,7 +16,7 @@
 	<parent>
 		<groupId>com.sap.cloud.security.xsuaa</groupId>
 		<artifactId>parent</artifactId>
-		<version>2.10.3</version>
+		<version>2.10.4</version>
 	</parent>
 
 	<artifactId>xsuaa-spring-boot-starter</artifactId>
diff --git a/spring-xsuaa/README.md b/spring-xsuaa/README.md
index aaf92ba1..c425c850 100644
--- a/spring-xsuaa/README.md
+++ b/spring-xsuaa/README.md
@@ -25,7 +25,7 @@ These (spring) dependencies needs to be provided:
 <dependency>
     <groupId>com.sap.cloud.security.xsuaa</groupId>
     <artifactId>spring-xsuaa</artifactId>
-    <version>2.10.3</version>
+    <version>2.10.4</version>
 </dependency>
 <dependency> <!-- new with version 1.5.0 -->
     <groupId>org.apache.logging.log4j</groupId>
@@ -39,7 +39,7 @@ These (spring) dependencies needs to be provided:
 <dependency>
     <groupId>com.sap.cloud.security.xsuaa</groupId>
     <artifactId>xsuaa-spring-boot-starter</artifactId>
-    <version>2.10.3</version>
+    <version>2.10.4</version>
 </dependency>
 ```
 
diff --git a/spring-xsuaa/pom.xml b/spring-xsuaa/pom.xml
index 64c74cfc..218f3a31 100644
--- a/spring-xsuaa/pom.xml
+++ b/spring-xsuaa/pom.xml
@@ -9,7 +9,7 @@
 	<parent>
 		<groupId>com.sap.cloud.security.xsuaa</groupId>
 		<artifactId>parent</artifactId>
-		<version>2.10.3</version>
+		<version>2.10.4</version>
 	</parent>
 
 	<artifactId>spring-xsuaa</artifactId>
diff --git a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServiceConfigurationDefault.java b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServiceConfigurationDefault.java
index 76c596a8..afdc07f9 100644
--- a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServiceConfigurationDefault.java
+++ b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServiceConfigurationDefault.java
@@ -12,7 +12,9 @@ import com.sap.cloud.security.config.CredentialType;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Configuration;
 
+import java.io.IOException;
 import java.net.URI;
+import java.util.Properties;
 
 @Configuration
 public class XsuaaServiceConfigurationDefault implements XsuaaServiceConfiguration {
@@ -29,9 +31,6 @@ public class XsuaaServiceConfigurationDefault implements XsuaaServiceConfigurati
 	@Value("${xsuaa.uaadomain:#{null}}")
 	private String uaadomain;
 
-	@Value("${xsuaa.identityzoneid:}")
-	private String identityZoneId;
-
 	@Value("${xsuaa.xsappname:}")
 	private String appid;
 
@@ -50,6 +49,8 @@ public class XsuaaServiceConfigurationDefault implements XsuaaServiceConfigurati
 	@Value("${xsuaa.certurl:#{null}}")
 	private String certUrl;
 
+	private Properties vcapServiceProperties;
+
 	/*
 	 * (non-Javadoc)
 	 *
@@ -102,4 +103,25 @@ public class XsuaaServiceConfigurationDefault implements XsuaaServiceConfigurati
 	public URI getCertUrl() {
 		return URI.create(certUrl);
 	}
+
+	private Properties getVcapServiceProperties() {
+		if(vcapServiceProperties == null) {
+			try {
+				vcapServiceProperties = new XsuaaServicesParser().parseCredentials();
+			} catch (IOException e) {
+				vcapServiceProperties = new Properties();
+			}
+		}
+		return vcapServiceProperties;
+	}
+
+	@Override
+	public String getProperty(String name) {
+		return getVcapServiceProperties().getProperty(name);
+	}
+
+	@Override
+	public boolean hasProperty(String name) {
+		return getVcapServiceProperties().containsKey(name);
+	}
 }
diff --git a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServicePropertySourceFactory.java b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServicePropertySourceFactory.java
index dfcc78e1..dfa60479 100644
--- a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServicePropertySourceFactory.java
+++ b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServicePropertySourceFactory.java
@@ -49,7 +49,7 @@ public class XsuaaServicePropertySourceFactory implements PropertySourceFactory
 	public static final String UAA_DOMAIN = "xsuaa.uaadomain";
 
 	private static final List<String> XSUAA_ATTRIBUTES = Arrays
-			.asList("clientid", "clientsecret", "identityzoneid",
+			.asList("clientid", "clientsecret",
 					"sburl", "tenantid", "tenantmode", "uaadomain", "url", "verificationkey", "xsappname",
 					"certificate",
 					"key", "credential-type", "certurl");
diff --git a/token-client/README.md b/token-client/README.md
index 774e8a89..f915cbdf 100644
--- a/token-client/README.md
+++ b/token-client/README.md
@@ -23,7 +23,7 @@ The Resource owner password credentials (i.e., username and password) can be use
 <dependency>
     <groupId>com.sap.cloud.security.xsuaa</groupId>
     <artifactId>token-client</artifactId>
-    <version>2.10.3</version>
+    <version>2.10.4</version>
 </dependency>
 <dependency>
   <groupId>org.apache.httpcomponents</groupId>
@@ -83,7 +83,7 @@ By default, the `DefaultOAuth2TokenService` caches tokens internally. The Cache
 <dependency>
     <groupId>com.sap.cloud.security.xsuaa</groupId>
     <artifactId>token-client</artifactId>
-    <version>2.10.3</version>
+    <version>2.10.4</version>
 </dependency>
 <dependency>
     <groupId>org.springframework</groupId>
@@ -136,7 +136,7 @@ In context of a Spring Boot application you may like to leverage auto-configurat
 <dependency>
     <groupId>com.sap.cloud.security.xsuaa</groupId>
     <artifactId>xsuaa-spring-boot-starter</artifactId>
-    <version>2.10.3</version>
+    <version>2.10.4</version>
 </dependency>
 <dependency> <!-- required when using Spring Web `RestTemplate` with X.509 authentication method-->
   <groupId>org.apache.httpcomponents</groupId>
diff --git a/token-client/pom.xml b/token-client/pom.xml
index 227b070a..78a7d8d9 100644
--- a/token-client/pom.xml
+++ b/token-client/pom.xml
@@ -9,7 +9,7 @@
 	<parent>
 		<groupId>com.sap.cloud.security.xsuaa</groupId>
 		<artifactId>parent</artifactId>
-		<version>2.10.3</version>
+		<version>2.10.4</version>
 	</parent>
 
 	<artifactId>token-client</artifactId>
diff --git a/token-client/src/main/java/com/sap/cloud/security/mtls/SSLContextFactory.java b/token-client/src/main/java/com/sap/cloud/security/mtls/SSLContextFactory.java
index f7874622..ac502008 100644
--- a/token-client/src/main/java/com/sap/cloud/security/mtls/SSLContextFactory.java
+++ b/token-client/src/main/java/com/sap/cloud/security/mtls/SSLContextFactory.java
@@ -45,12 +45,13 @@ public class SSLContextFactory {
 	 * Creates a SSLContext which can be used to parameterize your Rest client, in
 	 * order to support mutual TLS.
 	 *
-	 * @param x509Certificates,
+	 * @param x509Certificates
 	 *            you can get from your Service Configuration
-	 * @param rsaPrivateKey,
+	 * @param rsaPrivateKey
 	 *            you can get from your Service Configuration
 	 * @return a new SSLContext instance
 	 * @throws GeneralSecurityException
+	 *             in case of key parsing errors
 	 * @throws IOException
 	 *             in case of KeyStore initialization errors
 	 */
@@ -111,7 +112,7 @@ public class SSLContextFactory {
 
 		Collection<Certificate> certificateList = (Collection<Certificate>) factory
 				.generateCertificates(new ByteArrayInputStream(certificatesBytes));
-		return certificateList.toArray(new Certificate[certificateList.size()]);
+		return certificateList.toArray(new Certificate[0]);
 	}
 
 	private KeySpec parseDERPrivateKey(byte[] privateKeyDerEncoded)
diff --git a/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/ClientCredentials.java b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/ClientCredentials.java
new file mode 100644
index 00000000..a3f9e027
--- /dev/null
+++ b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/ClientCredentials.java
@@ -0,0 +1,19 @@
+package com.sap.cloud.security.xsuaa.client;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Deprecated in favor of {@link com.sap.cloud.security.config.ClientCredentials}
+ */
+@Deprecated
+public class ClientCredentials extends com.sap.cloud.security.config.ClientCredentials {
+    /**
+     * Specifies the OAuth 2.0 client.<br>
+     *
+     * @param clientId     - the ID of the OAuth 2.0 client requesting the token.
+     * @param clientSecret
+     */
+    public ClientCredentials(@Nonnull String clientId, @Nonnull String clientSecret) {
+        super(clientId, clientSecret);
+    }
+}
diff --git a/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/DefaultOAuth2TokenService.java b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/DefaultOAuth2TokenService.java
index b891813e..c567d2b0 100644
--- a/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/DefaultOAuth2TokenService.java
+++ b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/DefaultOAuth2TokenService.java
@@ -113,8 +113,9 @@ public class DefaultOAuth2TokenService extends AbstractOAuth2TokenService {
 		String accessToken = getParameter(accessTokenMap, ACCESS_TOKEN);
 		String refreshToken = getParameter(accessTokenMap, REFRESH_TOKEN);
 		String expiresIn = getParameter(accessTokenMap, EXPIRES_IN);
+		String tokenType = getParameter(accessTokenMap, TOKEN_TYPE);
 		return new OAuth2TokenResponse(accessToken, convertExpiresInToLong(expiresIn),
-				refreshToken);
+				refreshToken, tokenType);
 	}
 
 	private Long convertExpiresInToLong(String expiresIn) throws OAuth2ServiceException {
diff --git a/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/OAuth2TokenResponse.java b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/OAuth2TokenResponse.java
index f163cc30..d7675a48 100644
--- a/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/OAuth2TokenResponse.java
+++ b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/OAuth2TokenResponse.java
@@ -14,14 +14,22 @@ import java.util.Date;
 import java.util.concurrent.TimeUnit;
 
 public class OAuth2TokenResponse {
-	private String refreshToken;
-	private String accessToken;
-	private long expiredTimeMillis;
+	static final String TOKEN_TYPE_DEFAULT = "bearer";
+	private final String refreshToken;
+	private final String accessToken;
+	private final String tokenType;
+	private final long expiredTimeMillis;
 
 	public OAuth2TokenResponse(@Nullable String accessToken, long expiredInSeconds, @Nullable String refreshToken) {
+		this(accessToken, expiredInSeconds, refreshToken, TOKEN_TYPE_DEFAULT);
+	}
+
+	public OAuth2TokenResponse(@Nullable String accessToken, long expiredInSeconds, @Nullable String refreshToken,
+			String tokenType) {
 		this.accessToken = accessToken;
 		this.expiredTimeMillis = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(expiredInSeconds);
 		this.refreshToken = refreshToken;
+		this.tokenType = tokenType;
 	}
 
 	/**
@@ -82,9 +90,13 @@ public class OAuth2TokenResponse {
 		return refreshToken;
 	}
 
+	public String getTokenType() {
+		return tokenType;
+	}
+
 	@Override
 	public String toString() {
-		DecodedJwt decodedJwt = null;
+		DecodedJwt decodedJwt;
 		try {
 			decodedJwt = getDecodedAccessToken();
 		} catch (IllegalArgumentException e) {
diff --git a/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/OAuth2TokenServiceConstants.java b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/OAuth2TokenServiceConstants.java
index 02b21ad5..0663980d 100644
--- a/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/OAuth2TokenServiceConstants.java
+++ b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/OAuth2TokenServiceConstants.java
@@ -14,6 +14,7 @@ public class OAuth2TokenServiceConstants {
 	public static final String ACCESS_TOKEN = "access_token";
 	public static final String EXPIRES_IN = "expires_in";
 	public static final String REFRESH_TOKEN = "refresh_token";
+	public static final String TOKEN_TYPE = "token_type";
 	public static final String CLIENT_ID = "client_id";
 	public static final String CLIENT_SECRET = "client_secret";
 	public static final String USERNAME = "username";
diff --git a/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenService.java b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenService.java
index cf3aad59..d8db9f35 100644
--- a/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenService.java
+++ b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenService.java
@@ -106,7 +106,8 @@ public class XsuaaOAuth2TokenService extends AbstractOAuth2TokenService {
 		String accessToken = accessTokenMap.get(ACCESS_TOKEN);
 		long expiresIn = Long.parseLong(String.valueOf(accessTokenMap.get(EXPIRES_IN)));
 		String refreshToken = accessTokenMap.get(REFRESH_TOKEN);
-		return new OAuth2TokenResponse(accessToken, expiresIn, refreshToken);
+		String tokenType = accessTokenMap.get(TOKEN_TYPE);
+		return new OAuth2TokenResponse(accessToken, expiresIn, refreshToken, tokenType);
 	}
 
 	/**
diff --git a/token-client/src/main/java/com/sap/cloud/security/xsuaa/jwt/Base64JwtDecoder.java b/token-client/src/main/java/com/sap/cloud/security/xsuaa/jwt/Base64JwtDecoder.java
index 83cb1b02..8911e58b 100644
--- a/token-client/src/main/java/com/sap/cloud/security/xsuaa/jwt/Base64JwtDecoder.java
+++ b/token-client/src/main/java/com/sap/cloud/security/xsuaa/jwt/Base64JwtDecoder.java
@@ -59,10 +59,10 @@ public final class Base64JwtDecoder {
 
 	static class DecodedJwtImpl implements DecodedJwt {
 
-		private String header;
-		private String payload;
-		private String signature;
-		private String encodedJwt;
+		private final String header;
+		private final String payload;
+		private final String signature;
+		private final String encodedJwt;
 		private static final String TAB = "\t";
 		private static final Logger LOGGER = LoggerFactory.getLogger(DecodedJwtImpl.class);
 
@@ -71,6 +71,9 @@ public final class Base64JwtDecoder {
 			this.payload = payload;
 			this.signature = signature;
 			this.encodedJwt = encodedJwt;
+			if (LOGGER.isDebugEnabled()) {
+				LOGGER.debug(toString());
+			}
 		}
 
 		@Override
@@ -95,13 +98,10 @@ public final class Base64JwtDecoder {
 
 		@Override
 		public String toString() {
-			StringBuilder stringBuilder = new StringBuilder();
-			stringBuilder.append("Jwt header" + lineSeparator());
-			stringBuilder.append(TAB + getHeader() + lineSeparator());
-			stringBuilder.append("Jwt payload" + lineSeparator());
-			stringBuilder.append(TAB + getPayload() + lineSeparator());
-
-			return stringBuilder.toString();
+			return  "Jwt header" + lineSeparator() +
+					TAB + getHeader() + lineSeparator() +
+					"Jwt payload" + lineSeparator() +
+					TAB + getPayload() + lineSeparator();
 		}
 	}
 }


Test patch:
diff --git a/java-security-test/README.md b/java-security-test/README.md
index 4394ea62..1a8ade92 100644
--- a/java-security-test/README.md
+++ b/java-security-test/README.md
@@ -22,7 +22,7 @@ It includes for example a `JwtGenerator` that generates JSON Web Tokens (JWT) th
 <dependency>
     <groupId>com.sap.cloud.security</groupId>
     <artifactId>java-security-test</artifactId>
-    <version>2.10.3</version>
+    <version>2.10.4</version>
     <scope>test</scope>
 </dependency>
 ```
diff --git a/java-security-test/pom.xml b/java-security-test/pom.xml
index 8e599c12..fdcbf795 100644
--- a/java-security-test/pom.xml
+++ b/java-security-test/pom.xml
@@ -9,7 +9,7 @@
 	<parent>
 		<groupId>com.sap.cloud.security.xsuaa</groupId>
 		<artifactId>parent</artifactId>
-		<version>2.10.3</version>
+		<version>2.10.4</version>
 	</parent>
 
 	<groupId>com.sap.cloud.security</groupId>
@@ -18,7 +18,7 @@
 	<packaging>jar</packaging>
 
 	<properties>
-		<wiremock.version>2.29.1</wiremock.version>
+		<wiremock.version>2.30.1</wiremock.version>
 	</properties>
 
 	<dependencies>
diff --git a/java-security/src/test/java/com/sap/cloud/security/config/EnvironmentsTest.java b/java-security/src/test/java/com/sap/cloud/security/config/EnvironmentsTest.java
index 68ad3cd2..10ea2831 100644
--- a/java-security/src/test/java/com/sap/cloud/security/config/EnvironmentsTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/config/EnvironmentsTest.java
@@ -7,7 +7,10 @@ package com.sap.cloud.security.config;
 
 import com.sap.cloud.security.config.cf.CFConstants;
 import org.apache.commons.io.IOUtils;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import uk.org.webcompere.systemstubs.environment.EnvironmentVariables;
+import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -16,8 +19,11 @@ import static com.sap.cloud.security.config.cf.CFConstants.SERVICE_PLAN;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.assertj.core.api.Assertions.assertThat;
 
-public class EnvironmentsTest {
+@ExtendWith(SystemStubsExtension.class)
+class EnvironmentsTest {
 
+	private static final String KUBERNETES_SERVICE_HOST = "KUBERNETES_SERVICE_HOST";
+	private static final String K8S_HOST_VALUE = "0.0.0.0";
 	private final InputStream vcapMultipleXsuaa;
 
 	public EnvironmentsTest() throws IOException {
@@ -26,7 +32,7 @@ public class EnvironmentsTest {
 	}
 
 	@Test
-	public void getCurrent_returnsOnlySingleInstance() {
+	void getCurrent_returnsOnlySingleCFInstance() {
 		Environment firstEnvironment = Environments.getCurrent();
 		Environment secondEnvironment = Environments.getCurrent();
 
@@ -34,14 +40,30 @@ public class EnvironmentsTest {
 	}
 
 	@Test
-	public void getCurrent_returnsCorrectEnvironment() {
-		// TODO 29.11.19 c5295400: extend test when more than one environment is
-		// supported
+	 void getCurrent_returnsOnlySingleK8sInstance(EnvironmentVariables environmentVariables) {
+		environmentVariables.set(KUBERNETES_SERVICE_HOST, K8S_HOST_VALUE);
+
+		Environment firstEnvironment = Environments.getCurrent();
+		Environment secondEnvironment = Environments.getCurrent();
+
+		assertThat(firstEnvironment).isSameAs(secondEnvironment);
+		assertThat(firstEnvironment.getType()).isSameAs(Environment.Type.KUBERNETES);
+	}
+
+	@Test
+	 void getCurrent_returnsCf() {
 		assertThat(Environments.getCurrent().getType()).isEqualTo(Environment.Type.CF);
 	}
 
 	@Test
-	public void readFromInputMultipleInstances() {
+	void getCurrent_returnsK8s(EnvironmentVariables environmentVariables) {
+		environmentVariables.set(KUBERNETES_SERVICE_HOST, K8S_HOST_VALUE);
+		Environment cut = Environments.getCurrent();
+		assertThat(cut.getType()).isEqualTo(Environment.Type.KUBERNETES);
+	}
+
+	@Test
+	 void readFromInputMultipleInstances() {
 		Environment cut = Environments.readFromInput(vcapMultipleXsuaa);
 
 		assertThat(cut.getNumberOfXsuaaConfigurations()).isEqualTo(2);
@@ -60,7 +82,7 @@ public class EnvironmentsTest {
 	}
 
 	@Test
-	public void readFromInputDoesNotOverwriteCurrentEnvironment() {
+	 void readFromInputDoesNotOverwriteCurrentEnvironment() {
 		Environment cut = Environments.readFromInput(vcapMultipleXsuaa);
 
 		assertThat(cut).isNotSameAs(Environments.getCurrent());
diff --git a/spring-security/src/test/java/com/sap/cloud/security/spring/token/authentication/HybridJwtDecoderTest.java b/spring-security/src/test/java/com/sap/cloud/security/spring/token/authentication/HybridJwtDecoderTest.java
index 76ac467a..526ef650 100644
--- a/spring-security/src/test/java/com/sap/cloud/security/spring/token/authentication/HybridJwtDecoderTest.java
+++ b/spring-security/src/test/java/com/sap/cloud/security/spring/token/authentication/HybridJwtDecoderTest.java
@@ -70,11 +70,23 @@ class HybridJwtDecoderTest {
 	}
 
 	@Test
-	void decodeWithMissingExpClaim_throwsJsonParsingException() {
+	void decodeWithMissingExpClaim_throwsBadJwtException() {
 		String encodedToken = jwtGenerator
 				.withClaimValue(TokenClaims.EXPIRATION, "")
 				.createToken().getTokenValue();
 
-		assertThrows(JsonParsingException.class, () -> cut.decode(encodedToken));
+		assertThrows(BadJwtException.class, () -> cut.decode(encodedToken));
+	}
+
+	@Test
+	void decodeWithCorruptToken_throwsBadJwtException() {
+		String encodedToken = jwtGenerator
+				.withClaimValue(TokenClaims.EXPIRATION, "")
+				.createToken().getTokenValue();
+
+		assertThrows(BadJwtException.class, () -> cut.decode("Bearer e30="));
+		assertThrows(BadJwtException.class, () -> cut.decode("Bearer"));
+		assertThrows(BadJwtException.class, () -> cut.decode(null));
+		assertThrows(BadJwtException.class, () -> cut.decode("Bearerabc"));
 	}
 }
\ No newline at end of file
diff --git a/spring-xsuaa-test/README.md b/spring-xsuaa-test/README.md
index 7fe27e29..2664df06 100644
--- a/spring-xsuaa-test/README.md
+++ b/spring-xsuaa-test/README.md
@@ -31,7 +31,7 @@ This includes for example a `JwtGenerator` that generates JSON Web Tokens (JWT)
 <dependency>
     <groupId>com.sap.cloud.security.xsuaa</groupId>
     <artifactId>spring-xsuaa-test</artifactId>
-    <version>2.10.3</version>
+    <version>2.10.4</version>
     <scope>test</scope>
 </dependency>
 
diff --git a/spring-xsuaa-test/pom.xml b/spring-xsuaa-test/pom.xml
index 46a14379..5a3ca13c 100644
--- a/spring-xsuaa-test/pom.xml
+++ b/spring-xsuaa-test/pom.xml
@@ -9,7 +9,7 @@
 	<parent>
 		<groupId>com.sap.cloud.security.xsuaa</groupId>
 		<artifactId>parent</artifactId>
-		<version>2.10.3</version>
+		<version>2.10.4</version>
 	</parent>
 
 	<artifactId>spring-xsuaa-test</artifactId>
diff --git a/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/XsuaaServicesParserTest.java b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/XsuaaServicesParserTest.java
index 904291b3..b711d002 100644
--- a/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/XsuaaServicesParserTest.java
+++ b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/XsuaaServicesParserTest.java
@@ -39,4 +39,15 @@ public class XsuaaServicesParserTest {
 		Properties properties = cut.parseCredentials();
 		assertThat(properties.getProperty("clientid")).isEqualTo("client-id");
 	}
+
+	@Test
+	public void acceptOtherVcapServicesProperties() throws IOException {
+		String vcapWithAddProperties = "{\"xsuaa\":[{\"credentials\":{\"apiurl\":\"https://api.mydomain.com\",\"tenantid\":\"tenant-id\",\"clientid\":\"client-id\"},\"tags\":[\"xsuaa\"]}]}";
+		XsuaaServicesParser cut = new XsuaaServicesParser(vcapWithAddProperties);
+		Properties properties = cut.parseCredentials();
+		assertThat(properties.getProperty("apiurl")).isEqualTo("https://api.mydomain.com");
+		assertThat(properties.containsKey("apiurl")).isTrue();
+		assertThat(properties.getProperty("tenantid")).isEqualTo("tenant-id");
+		assertThat(properties.getProperty("clientid")).isEqualTo("client-id");
+	}
 }
diff --git a/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/DefaultOAuth2TokenServiceTest.java b/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/DefaultOAuth2TokenServiceTest.java
index 244a774d..1b82121b 100644
--- a/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/DefaultOAuth2TokenServiceTest.java
+++ b/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/DefaultOAuth2TokenServiceTest.java
@@ -40,9 +40,10 @@ public class DefaultOAuth2TokenServiceTest {
 
 	private static final String ACCESS_TOKEN = "abc123";
 	private static final String REFRESH_TOKEN = "def456";
+	private static final String TOKEN_TYPE = "bearer";
 	private static final String VALID_JSON_RESPONSE = String
-			.format("{expires_in: 10000, access_token: %s, refresh_token: %s}",
-					ACCESS_TOKEN, REFRESH_TOKEN);
+			.format("{expires_in: 10000, access_token: %s, refresh_token: %s, token_type: %s}",
+					ACCESS_TOKEN, REFRESH_TOKEN, TOKEN_TYPE);
 	private static final URI TOKEN_ENDPOINT_URI = URI.create("https://subdomain.myauth.server.com/oauth/token");
 
 	private CloseableHttpClient mockHttpClient;
@@ -74,6 +75,7 @@ public class DefaultOAuth2TokenServiceTest {
 		assertThat(re.getAccessToken()).isEqualTo(ACCESS_TOKEN);
 		assertThat(re.getRefreshToken()).isEqualTo(REFRESH_TOKEN);
 		assertThat(re.getExpiredAt()).isAfter(Instant.now());
+		assertThat(re.getTokenType()).isEqualTo(TOKEN_TYPE);
 	}
 
 	@Test
diff --git a/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/OAuth2TokenResponseTest.java b/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/OAuth2TokenResponseTest.java
index f93d9538..61169966 100644
--- a/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/OAuth2TokenResponseTest.java
+++ b/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/OAuth2TokenResponseTest.java
@@ -13,6 +13,7 @@ import java.util.Date;
 import java.util.concurrent.TimeUnit;
 
 import static org.hamcrest.CoreMatchers.allOf;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 
 public class OAuth2TokenResponseTest {
@@ -42,6 +43,13 @@ public class OAuth2TokenResponseTest {
 				OrderingComparison.lessThanOrEqualTo(maxExpireDate)));
 	}
 
+	@Test
+	public void getTokenType() {
+		OAuth2TokenResponse tokenResponse = new OAuth2TokenResponse("accessToken", 47299, null, "bearer");
+		assertEquals("bearer", tokenResponse.getTokenType());
+		assertEquals("accessToken", tokenResponse.getAccessToken());
+	}
+
 	private Instant getCurrentInstant() {
 		return Instant.ofEpochMilli(System.currentTimeMillis());
 	}
diff --git a/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServiceClientCredentialsTest.java b/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServiceClientCredentialsTest.java
index 7806f5ca..d0c2b184 100644
--- a/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServiceClientCredentialsTest.java
+++ b/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServiceClientCredentialsTest.java
@@ -5,22 +5,6 @@
  */
 package com.sap.cloud.security.xsuaa.client;
 
-import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.ACCESS_TOKEN;
-import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.EXPIRES_IN;
-import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.GRANT_TYPE;
-import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.GRANT_TYPE_CLIENT_CREDENTIALS;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertNotNull;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.eq;
-
-import java.net.URI;
-import java.util.HashMap;
-import java.util.Map;
-
 import com.sap.cloud.security.config.ClientCredentials;
 import com.sap.cloud.security.config.ClientIdentity;
 import org.junit.Before;
@@ -35,6 +19,19 @@ import org.springframework.http.ResponseEntity;
 import org.springframework.web.client.HttpClientErrorException;
 import org.springframework.web.client.RestOperations;
 
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.*;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+
 @RunWith(MockitoJUnitRunner.class)
 public class XsuaaOAuth2TokenServiceClientCredentialsTest {
 
@@ -55,6 +52,7 @@ public class XsuaaOAuth2TokenServiceClientCredentialsTest {
 		responseMap = new HashMap<>();
 		responseMap.putIfAbsent(ACCESS_TOKEN, "f529.dd6e30.d454677322aaabb0");
 		responseMap.putIfAbsent(EXPIRES_IN, "43199");
+		responseMap.putIfAbsent(TOKEN_TYPE, "bearer");
 	}
 
 	@Test
@@ -99,6 +97,7 @@ public class XsuaaOAuth2TokenServiceClientCredentialsTest {
 				clientIdentity,
 				null, null);
 		assertThat(accessToken.getAccessToken(), is(responseMap.get(ACCESS_TOKEN)));
+		assertThat(accessToken.getTokenType(), is(responseMap.get(TOKEN_TYPE)));
 		assertNotNull(accessToken.getExpiredAt());
 	}
 
diff --git a/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServiceJwtBearerTokenTest.java b/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServiceJwtBearerTokenTest.java
index f5a5d942..baeb5ee5 100644
--- a/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServiceJwtBearerTokenTest.java
+++ b/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServiceJwtBearerTokenTest.java
@@ -51,6 +51,7 @@ public class XsuaaOAuth2TokenServiceJwtBearerTokenTest {
 		response = new HashMap<>();
 		response.putIfAbsent(ACCESS_TOKEN, "f529.dd6e30.d454677322aaabb0");
 		response.putIfAbsent(EXPIRES_IN, "43199");
+		response.putIfAbsent(TOKEN_TYPE, "bearer");
 		when(mockRestOperations.postForEntity(any(), any(), any()))
 				.thenReturn(ResponseEntity.status(200).body(response));
 		optionalParameters = new HashMap<>();
@@ -161,6 +162,7 @@ public class XsuaaOAuth2TokenServiceJwtBearerTokenTest {
 				jwtToken, null, null);
 
 		assertThat(actualResponse.getAccessToken()).isEqualTo(response.get(ACCESS_TOKEN));
+		assertThat(actualResponse.getTokenType()).isEqualTo(response.get(TOKEN_TYPE));
 		assertThat(actualResponse.getExpiredAt()).isNotNull();
 	}
 
diff --git a/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServicePasswordTest.java b/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServicePasswordTest.java
index 6bc37e2a..b4301290 100644
--- a/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServicePasswordTest.java
+++ b/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServicePasswordTest.java
@@ -54,6 +54,7 @@ public class XsuaaOAuth2TokenServicePasswordTest {
 		response = new HashMap();
 		response.putIfAbsent(ACCESS_TOKEN, "f529.dd6e30.d454677322aaabb0");
 		response.putIfAbsent(EXPIRES_IN, "43199");
+		response.putIfAbsent(TOKEN_TYPE, "bearer");
 		when(mockRestOperations.postForEntity(any(), any(), any()))
 				.thenReturn(ResponseEntity.status(200).body(response));
 		optionalParameters = new HashMap<>();
@@ -175,7 +176,7 @@ public class XsuaaOAuth2TokenServicePasswordTest {
 				username, password, null, null);
 
 		assertThat(actualResponse.getAccessToken()).isEqualTo(response.get(ACCESS_TOKEN));
-
+		assertThat(actualResponse.getTokenType()).isEqualTo(response.get(TOKEN_TYPE));
 		assertThat(actualResponse.getExpiredAtDate()).isNotNull();
 	}
 
diff --git a/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServiceRefreshTokenTest.java b/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServiceRefreshTokenTest.java
index 2099dbe4..acff2b32 100644
--- a/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServiceRefreshTokenTest.java
+++ b/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServiceRefreshTokenTest.java
@@ -5,10 +5,7 @@
  */
 package com.sap.cloud.security.xsuaa.client;
 
-import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.ACCESS_TOKEN;
-import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.EXPIRES_IN;
-import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.GRANT_TYPE_REFRESH_TOKEN;
-import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.REFRESH_TOKEN;
+import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.*;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertNotNull;
@@ -58,6 +55,7 @@ public class XsuaaOAuth2TokenServiceRefreshTokenTest {
 		responseMap.put(REFRESH_TOKEN, "2170b564228448c6aed8b1ddfdb8bf53-r");
 		responseMap.put(ACCESS_TOKEN, "f529.dd6e30.d454677322aaabb0");
 		responseMap.put(EXPIRES_IN, "43199");
+		responseMap.put(TOKEN_TYPE, "bearer");
 	}
 
 	@Test
@@ -106,6 +104,7 @@ public class XsuaaOAuth2TokenServiceRefreshTokenTest {
 				refreshToken, null);
 		assertThat(accessToken.getRefreshToken(), is(responseMap.get(REFRESH_TOKEN)));
 		assertThat(accessToken.getAccessToken(), is(responseMap.get(ACCESS_TOKEN)));
+		assertThat(accessToken.getTokenType(), is(responseMap.get(TOKEN_TYPE)));
 		assertNotNull(accessToken.getExpiredAt());
 	}
 }
\ No newline at end of file
diff --git a/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServiceUserTokenTest.java b/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServiceUserTokenTest.java
index e8a3d3bd..76d284e7 100644
--- a/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServiceUserTokenTest.java
+++ b/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServiceUserTokenTest.java
@@ -5,21 +5,6 @@
  */
 package com.sap.cloud.security.xsuaa.client;
 
-import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.ACCESS_TOKEN;
-import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.EXPIRES_IN;
-import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.REFRESH_TOKEN;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertNotNull;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.eq;
-
-import java.net.URI;
-import java.util.HashMap;
-import java.util.Map;
-
 import com.sap.cloud.security.config.ClientCredentials;
 import com.sap.cloud.security.config.ClientIdentity;
 import org.junit.Before;
@@ -35,6 +20,19 @@ import org.springframework.http.ResponseEntity;
 import org.springframework.web.client.HttpClientErrorException;
 import org.springframework.web.client.RestOperations;
 
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.*;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+
 @RunWith(MockitoJUnitRunner.class)
 public class XsuaaOAuth2TokenServiceUserTokenTest {
 
@@ -57,6 +55,7 @@ public class XsuaaOAuth2TokenServiceUserTokenTest {
 		responseMap.put(REFRESH_TOKEN, "2170b564228448c6aed8b1ddfdb8bf53-r");
 		responseMap.put(ACCESS_TOKEN, "4d841646fcc340f59b1b7b43df4b050d"); // opaque access token
 		responseMap.put(EXPIRES_IN, "43199");
+		responseMap.put(TOKEN_TYPE, "bearer");
 	}
 
 	@Test
@@ -111,6 +110,7 @@ public class XsuaaOAuth2TokenServiceUserTokenTest {
 				userTokenToBeExchanged, null, null);
 		assertThat(accessToken.getRefreshToken(), is(responseMap.get(REFRESH_TOKEN)));
 		assertThat(accessToken.getAccessToken(), is(responseMap.get(ACCESS_TOKEN)));
+		assertThat(accessToken.getTokenType(), is(responseMap.get(TOKEN_TYPE)));
 		assertNotNull(accessToken.getExpiredAt());
 	}