Repository: SAP/cloud-security-xsuaa-integration
Pull request #490 (2021-03-25 09:05:15)

ProofOfPossession X5t validator


Patch:
diff --git a/java-api/pom.xml b/java-api/pom.xml
index 7998e7d6..3a7ef4f6 100644
--- a/java-api/pom.xml
+++ b/java-api/pom.xml
@@ -69,6 +69,11 @@
 			<artifactId>commons-io</artifactId>
 			<scope>test</scope>
 		</dependency>
+		<dependency> <!-- check if it's still needed when slf4j-api 1.8 is available -->
+			<groupId>org.slf4j</groupId>
+			<artifactId>slf4j-simple</artifactId>
+			<scope>test</scope>
+		</dependency>
 	</dependencies>
 
 	<build>
diff --git a/java-api/src/main/java/com/sap/cloud/security/servlet/MDCConstants.java b/java-api/src/main/java/com/sap/cloud/security/servlet/MDCConstants.java
new file mode 100644
index 00000000..bd33ffaa
--- /dev/null
+++ b/java-api/src/main/java/com/sap/cloud/security/servlet/MDCConstants.java
@@ -0,0 +1,18 @@
+/**
+ * SPDX-FileCopyrightText: 2018-2021 SAP SE or an SAP affiliate company and Cloud Security Client Java contributors
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package com.sap.cloud.security.servlet;
+
+
+/**
+ * Constants for Mapped Diagnostic Context.
+ */
+public class MDCConstants {
+
+    public static final String CORRELATION_ID = "correlation_id";
+    public static final String CORRELATION_HEADER = "X-CorrelationID";
+
+    private MDCConstants() {}
+}
diff --git a/java-api/src/main/java/com/sap/cloud/security/servlet/MDCHelper.java b/java-api/src/main/java/com/sap/cloud/security/servlet/MDCHelper.java
new file mode 100644
index 00000000..51dd2cd8
--- /dev/null
+++ b/java-api/src/main/java/com/sap/cloud/security/servlet/MDCHelper.java
@@ -0,0 +1,40 @@
+/**
+ * SPDX-FileCopyrightText: 2018-2021 SAP SE or an SAP affiliate company and Cloud Security Client Java contributors
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package com.sap.cloud.security.servlet;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.MDC;
+
+import java.util.UUID;
+
+import static com.sap.cloud.security.servlet.MDCConstants.CORRELATION_ID;
+
+/**
+ * The Mapped Diagnostic Context helper class.
+ */
+public final class MDCHelper {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(MDCHelper.class);
+
+    private MDCHelper() {}
+
+    /**
+     * Gets correlation_id from MDC, if it is missing, new correlation_id will be created.
+     *
+     * @return the string of correlation_id
+     */
+    public static String getOrCreateCorrelationId(){
+        String correlationId = MDC.get(CORRELATION_ID);
+        if (correlationId == null || correlationId.isEmpty()){
+            correlationId = String.valueOf(UUID.randomUUID());
+            LOGGER.info("Correlation id (key={}) was not found in the MDC, generating a new one: {}", CORRELATION_ID, correlationId);
+        } else {
+            LOGGER.debug("Correlation id (key={}) from MDC: {}", CORRELATION_ID, correlationId);
+        }
+        return correlationId;
+    }
+}
diff --git a/java-api/src/main/java/com/sap/cloud/security/token/SecurityContext.java b/java-api/src/main/java/com/sap/cloud/security/token/SecurityContext.java
index 830ce3b2..23710736 100644
--- a/java-api/src/main/java/com/sap/cloud/security/token/SecurityContext.java
+++ b/java-api/src/main/java/com/sap/cloud/security/token/SecurityContext.java
@@ -9,6 +9,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import javax.annotation.Nullable;
+import java.security.cert.X509Certificate;
 
 /**
  * Thread wide {@link Token} storage.
@@ -20,6 +21,41 @@ public class SecurityContext {
 	}
 
 	private static final ThreadLocal<Token> tokenStorage = new ThreadLocal<>();
+	private static final ThreadLocal<X509Certificate> certificateStorage = new ThreadLocal<>();
+
+	/**
+	 * Returns the certificate that is saved in thread wide storage.
+	 *
+	 *
+	 * @return the certificate or null if the storage is empty.
+	 */
+	@Nullable
+	public static X509Certificate getClientCertificate() {
+		return certificateStorage.get();
+	}
+
+	/**
+	 * Saves the certificate thread wide.
+	 *
+	 * @param certificate
+	 *            certificate to be saved.
+	 */
+	public static void setClientCertificate(X509Certificate certificate) {
+		LOGGER.info("Sets certificate to SecurityContext (thread-locally). {}",
+				certificate);
+		certificateStorage.set(certificate);
+	}
+
+	/**
+	 * Clears the current Certificate from thread wide storage.
+	 */
+	private static void clearCertificate() {
+		final java.security.cert.X509Certificate certificate = certificateStorage.get();
+		if (certificate != null) {
+			LOGGER.debug("Certificate removed from SecurityContext (thread-locally).");
+			certificateStorage.remove();
+		}
+	}
 
 	/**
 	 * Saves the validated (!) token thread wide.
@@ -67,4 +103,12 @@ public class SecurityContext {
 		}
 	}
 
+	/**
+	 * Clears the current token and certificate from thread wide storage.
+	 */
+	public static void clear() {
+		clearCertificate();
+		clearToken();
+	}
+
 }
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 4204a2c3..af6e5233 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
@@ -43,8 +43,8 @@ public final class TokenClaims {
 	public static final String SAP_GLOBAL_ZONE_ID = "zone_uuid"; // tenant GUID
 	public static final String GROUPS = "groups"; // scim groups
 	public static final String AUTHORIZATION_PARTY = "azp"; // Authorization party contains OAuth client identifier
-	static final String CNF = "cnf"; // X509 certificate ("cnf" (confirmation)) claim
-	static final String CNF_X509_THUMBPRINT = "x5t#S256"; // X509 certificate thumbprint confirmation method
+	public static final String CNF = "cnf"; // X509 certificate ("cnf" (confirmation)) claim
+	public static final String CNF_X5T = "x5t#S256"; // X509 certificate thumbprint confirmation method
 
 	public final class XSUAA {
 
diff --git a/java-api/src/main/java/com/sap/cloud/security/x509/X509Constants.java b/java-api/src/main/java/com/sap/cloud/security/x509/X509Constants.java
new file mode 100644
index 00000000..0f51f787
--- /dev/null
+++ b/java-api/src/main/java/com/sap/cloud/security/x509/X509Constants.java
@@ -0,0 +1,14 @@
+/**
+ * SPDX-FileCopyrightText: 2018-2021 SAP SE or an SAP affiliate company and Cloud Security Client Java contributors
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package com.sap.cloud.security.x509;
+
+public final class X509Constants {
+
+	private X509Constants() {
+	}
+
+	public static final String FWD_CLIENT_CERT_HEADER = "x-forwarded-client-cert";
+}
diff --git a/java-api/src/main/java/com/sap/cloud/security/x509/X509Parser.java b/java-api/src/main/java/com/sap/cloud/security/x509/X509Parser.java
new file mode 100644
index 00000000..762afb4c
--- /dev/null
+++ b/java-api/src/main/java/com/sap/cloud/security/x509/X509Parser.java
@@ -0,0 +1,98 @@
+/**
+ * SPDX-FileCopyrightText: 2018-2021 SAP SE or an SAP affiliate company and Cloud Security Client Java contributors
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package com.sap.cloud.security.x509;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nonnull;
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Base64;
+import java.util.Objects;
+
+public class X509Parser {
+	public static final Logger LOGGER = LoggerFactory.getLogger(X509Parser.class);
+	public static final String BEGIN_CERTIFICATE = "-----BEGIN CERTIFICATE-----";
+	public static final String END_CERTIFICATE = "-----END CERTIFICATE-----";
+
+	private X509Parser() {
+	}
+
+	/**
+	 * Decodes X509 base64 encoded certificate into X509Certificate class
+	 * 
+	 * @param encodedX509
+	 *            base64 encoded X509 certificate
+	 * @return Decoded X509Certificate
+	 * @throws CertificateException
+	 *             if String value of certificate cannot be parsed
+	 */
+	public static X509Certificate parseCertificate(@Nonnull String encodedX509) throws CertificateException {
+		String encodedPemX509 = formatBase64Cert(encodedX509);
+		CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+		ByteArrayInputStream bytes = new ByteArrayInputStream(encodedPemX509.getBytes(StandardCharsets.UTF_8));
+		return (X509Certificate) certFactory.generateCertificate(bytes);
+	}
+
+	/**
+	 * Generates X509 thumbprint from provided certificate.
+	 *
+	 * @see <a href="https://tools.ietf.org/html/rfc8705#section-3.1"></a>
+	 * @param x509Certificate
+	 *            X509certificate object
+	 * @return Thumbprint of X509 certificate
+	 * @throws NoSuchAlgorithmException
+	 *             when a particular cryptographic algorithm is requested but is not
+	 *             available in the environment
+	 * @throws CertificateEncodingException
+	 *             is thrown if error occurs while encoding X509 certificatejava
+	 */
+	public static String getCertificateThumbprint(X509Certificate x509Certificate)
+			throws NoSuchAlgorithmException, CertificateEncodingException {
+		MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
+		byte[] hashedX509 = sha256.digest(x509Certificate.getEncoded());
+		return Base64.getUrlEncoder().withoutPadding().encodeToString(hashedX509);
+	}
+
+	/**
+	 * Formats the base64 encoded certificate to <a href=
+	 * "https://docs.oracle.com/javase/8/docs/api/java/security/cert/CertificateFactory.html">JDK
+	 * 1.8</a> required format.
+	 *
+	 * @param base64EncodedCert
+	 *            the base64 encoded certificate
+	 * @return formatted Base64 string surrounded by PEM labels
+	 */
+	static String formatBase64Cert(@Nonnull String base64EncodedCert) {
+		Objects.requireNonNull(base64EncodedCert, "The provided Certificate can not be null");
+		String cert = base64EncodedCert
+				.replace("\\n", "")
+				.replace(BEGIN_CERTIFICATE, BEGIN_CERTIFICATE + "\n")
+				.replace(END_CERTIFICATE, "\n" + END_CERTIFICATE + "\n")
+				.replaceAll("\\n$", "");
+		return encodePemLabels(cert);
+	}
+
+	@Nonnull
+	private static String encodePemLabels(String base64EncodedCert) {
+		if (!base64EncodedCert.startsWith(BEGIN_CERTIFICATE)) {
+			base64EncodedCert = BEGIN_CERTIFICATE + "\n" + base64EncodedCert;
+		}
+		if (!base64EncodedCert.endsWith(END_CERTIFICATE)) {
+			base64EncodedCert = base64EncodedCert + "\n" + END_CERTIFICATE;
+		}
+		LOGGER.debug("PEM encoded certificate: {}", base64EncodedCert);
+		return base64EncodedCert;
+	}
+
+}
diff --git a/java-security/README.md b/java-security/README.md
index 96a37777..6deddf18 100644
--- a/java-security/README.md
+++ b/java-security/README.md
@@ -79,7 +79,7 @@ OAuth2ServiceConfiguration serviceConfig = Environments.getCurrent().getXsuaaCon
 ```
 > Note: By default `Environments` auto-detects the environment: Cloud Foundry or Kubernetes. 
 
-Alternatively you can also specify the Service Configuration by your own:
+Alternatively, you can also specify your own Service Configuration:
 ```java
 OAuth2ServiceConfiguration serviceConfig = OAuth2ServiceConfigurationBuilder.forService(Service.XSUAA)
       .withProperty(CFConstants.XSUAA.APP_ID, "appid")
@@ -111,7 +111,7 @@ CombiningValidator<Token> validators = JwtValidatorBuilder.getInstance(serviceCo
 
 > For the Signature validation it needs to fetch the Json Web Token Keys (jwks) from the OAuth server. In case the token does not provide a `jku` header parameter it also requests the Open-ID Provider Configuration from the OAuth Server to determine the `jwks_uri`. The used Apache Rest client can be customized via the `JwtValidatorBuilder` builder.  
 
-> Furthermore the token keys fetched from the Identity Service are cached for about 10 minutes. You may like to overwrite the cache [default configuration](/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/TokenKeyCacheConfiguration.java#L14) with `JwtValidatorBuilder.withCacheConfiguration()`.  
+> Furthermore, the token keys fetched from the Identity Service are cached for about 10 minutes. You may like to overwrite the cache [default configuration](/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/TokenKeyCacheConfiguration.java#L14) with `JwtValidatorBuilder.withCacheConfiguration()`.  
 
 #### [Optional] Step 2.1: Add Validation Listeners for Audit Log
 Optionally, you can add a validation listener to the validator to be able to get called back whenever a token is validated. Here you may want to emit logs to the audit log service.
@@ -179,6 +179,26 @@ For the integration of different Identity Services the [`TokenAuthenticator`](/j
 
 ![](images/xsuaaFilter.png)
 
+
+### ProofOfPossession validation 
+#### X509 certificate thumbprint `X5t` validation
+[JwtX5tValidator](src/main/java/com/sap/cloud/security/token/validation/validators/JwtX5tValidator.java) offers JWT Certificate Thumbprint `X5t` confirmation method's validation. See specification [here](https://tools.ietf.org/html/rfc8705#section-3.1). 
+This validator is not part of the default `CombiningValidator`, it needs to be added manually to `JwtValidatorBuilder` to use it. 
+It can be done in the following manner:
+```java
+JwtValidatorBuilder.getInstance(OAuth2ServiceConfiguration)
+    .with(new JwtX5tValidator())
+    .build();
+```
+
+#### Troubleshooting 
+In case of invalid response i.e 401 or 403 error codes, check application error logs for detailed messages. 
+
+Common reasons for failed validation:
+- invalid X509 certificate -> `CertificateException` is thrown when parsing of X509 certificate failed
+- X509 certificate is missing from the `SecurityContext`
+- `cnf` claim is missing from incoming request 
+
 ## Test Utilities
 You can find the JUnit test utilities documented [here](/java-security-test).
 
diff --git a/java-security/src/main/java/com/sap/cloud/security/servlet/IasTokenAuthenticator.java b/java-security/src/main/java/com/sap/cloud/security/servlet/IasTokenAuthenticator.java
index 4f82a80d..a1f81a97 100644
--- a/java-security/src/main/java/com/sap/cloud/security/servlet/IasTokenAuthenticator.java
+++ b/java-security/src/main/java/com/sap/cloud/security/servlet/IasTokenAuthenticator.java
@@ -8,9 +8,14 @@ package com.sap.cloud.security.servlet;
 import com.sap.cloud.security.config.Environments;
 import com.sap.cloud.security.config.OAuth2ServiceConfiguration;
 import com.sap.cloud.security.token.SapIdToken;
+import com.sap.cloud.security.token.SecurityContext;
 import com.sap.cloud.security.token.Token;
+import com.sap.cloud.security.x509.X509CertificateExtractor;
 
 import javax.annotation.Nullable;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
 
 public class IasTokenAuthenticator extends AbstractTokenAuthenticator {
 
@@ -19,6 +24,13 @@ public class IasTokenAuthenticator extends AbstractTokenAuthenticator {
 		return new SapIdToken(authorizationHeader);
 	}
 
+	@Override
+	public TokenAuthenticationResult validateRequest(ServletRequest request, ServletResponse response) {
+		HttpServletRequest httpRequest = (HttpServletRequest) request;
+		SecurityContext.setClientCertificate(X509CertificateExtractor.getInstance().getClientCertificate(httpRequest));
+		return super.validateRequest(request, response);
+	}
+
 	@Override
 	protected OAuth2ServiceConfiguration getServiceConfiguration() {
 		OAuth2ServiceConfiguration config = serviceConfiguration != null ? serviceConfiguration
@@ -34,4 +46,5 @@ public class IasTokenAuthenticator extends AbstractTokenAuthenticator {
 	protected OAuth2ServiceConfiguration getOtherServiceConfiguration() {
 		return null;
 	}
+
 }
diff --git a/java-security/src/main/java/com/sap/cloud/security/servlet/XsuaaTokenAuthenticator.java b/java-security/src/main/java/com/sap/cloud/security/servlet/XsuaaTokenAuthenticator.java
index 496422f4..c2e2e30d 100644
--- a/java-security/src/main/java/com/sap/cloud/security/servlet/XsuaaTokenAuthenticator.java
+++ b/java-security/src/main/java/com/sap/cloud/security/servlet/XsuaaTokenAuthenticator.java
@@ -11,6 +11,7 @@ import com.sap.cloud.security.config.OAuth2ServiceConfiguration;
 import com.sap.cloud.security.config.Service;
 import com.sap.cloud.security.config.cf.CFConstants;
 import com.sap.cloud.security.token.*;
+import com.sap.cloud.security.x509.X509CertificateExtractor;
 import com.sap.cloud.security.xsuaa.client.DefaultOAuth2TokenService;
 import com.sap.cloud.security.xsuaa.http.HttpHeaders;
 import org.apache.http.impl.client.CloseableHttpClient;
@@ -105,6 +106,7 @@ public class XsuaaTokenAuthenticator extends AbstractTokenAuthenticator {
 			String authorizationHeader = httpRequest.getHeader(HttpHeaders.AUTHORIZATION);
 			if (headerIsAvailable(authorizationHeader)) {
 				try {
+					SecurityContext.setClientCertificate(X509CertificateExtractor.getInstance().getClientCertificate(httpRequest));
 					Token token = Token.create(authorizationHeader);
 					if (token.getService() == Service.IAS) {
 						if (exchangeBroker == null) {
diff --git a/java-security/src/main/java/com/sap/cloud/security/token/SapIdToken.java b/java-security/src/main/java/com/sap/cloud/security/token/SapIdToken.java
index 4d7b2147..3e68f549 100644
--- a/java-security/src/main/java/com/sap/cloud/security/token/SapIdToken.java
+++ b/java-security/src/main/java/com/sap/cloud/security/token/SapIdToken.java
@@ -64,6 +64,6 @@ public class SapIdToken extends AbstractToken {
 
 	@Nullable
 	public String getCnfX509Thumbprint() {
-		return getAttributeFromClaimAsString(TokenClaims.CNF, TokenClaims.CNF_X509_THUMBPRINT);
+		return getAttributeFromClaimAsString(TokenClaims.CNF, TokenClaims.CNF_X5T);
 	}
 }
diff --git a/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtX5tValidator.java b/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtX5tValidator.java
new file mode 100644
index 00000000..0b80451b
--- /dev/null
+++ b/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtX5tValidator.java
@@ -0,0 +1,88 @@
+package com.sap.cloud.security.token.validation.validators;
+
+import com.sap.cloud.security.json.JsonObject;
+import com.sap.cloud.security.token.SecurityContext;
+import com.sap.cloud.security.token.Token;
+import com.sap.cloud.security.token.TokenClaims;
+import com.sap.cloud.security.token.validation.ValidationResult;
+import com.sap.cloud.security.token.validation.ValidationResults;
+import com.sap.cloud.security.token.validation.Validator;
+import com.sap.cloud.security.x509.X509Parser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nullable;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+/**
+ * Validates if the jwt access token is intended for the OAuth2 client of this
+ * application, in certificate based authentication scenario. The cnf claim
+ * provides the SHA-256 thumbprint of the X.509 certificate information, this
+ * information needs to match the thumbprint of the provided certificate.
+ * <p>
+ * Validates whether there 'cnf' thumbprint value matches with the X509
+ * certificate from the request.
+ *
+ * Validator is by default disabled. It can be activated by setting environment
+ * variable 'X5T_VALIDATOR_ENABLED' to true.
+ */
+public class JwtX5tValidator implements Validator<Token> {
+
+	private static final Logger LOGGER = LoggerFactory.getLogger(JwtX5tValidator.class);
+
+	/**
+	 * Validates the cnf thumbprint of X509 certificate against trusted
+	 * certificate's thumbprint.
+	 *
+	 * In case audience contains only a single value, thumbprint comparison is not
+	 * performed and request is validated. To guarantee that this single audience is
+	 * trusted, use this validator in combination with {@link JwtAudienceValidator}
+	 *
+	 * @param token
+	 *            token to be validated
+	 * @return validation result. Result is valid when both thumbprints match in
+	 *         case of multiple audiences.
+	 */
+	@Override
+	public ValidationResult validate(Token token) {
+		String tokenX5t = extractCnfThumbprintFromToken(token);
+		LOGGER.debug("Token 'cnf' thumbprint: {}", tokenX5t);
+		if (tokenX5t != null) {
+			X509Certificate clientCertificate = SecurityContext.getClientCertificate();
+			if (clientCertificate == null) {
+				LOGGER.error("Client certificate missing from SecurityContext");
+				return ValidationResults.createInvalid("Certificate validation failed");
+			}
+			try {
+				String clientCertificateX5t = X509Parser.getCertificateThumbprint(clientCertificate);
+				if (clientCertificateX5t.equals(tokenX5t)) {
+					// TODO add audience check (azp client id of the caller and
+					// client id of the bound identity) when clarified
+					return ValidationResults.createValid();
+				}
+				LOGGER.error("Thumbprint from token {} != thumbprint from client certificate {}", tokenX5t,
+						clientCertificateX5t);
+			} catch (NoSuchAlgorithmException | CertificateException e) {
+				LOGGER.error("Couldn't generate x509 thumbprint from client certificate. {}", e.getMessage(), e);
+			}
+		}
+		return ValidationResults.createInvalid("Certificate validation failed");
+	}
+
+	/**
+	 * Extract cnf attribute value of 'x5t#S256' (SHA-256 hashed certificate
+	 * thumbprint) from token.
+	 *
+	 * @param token
+	 *            received token
+	 * @return cnf value of 'x5t#S256' or null if value is not present
+	 */
+	@Nullable
+	private static String extractCnfThumbprintFromToken(Token token) {
+		JsonObject cnf = token.getClaimAsJsonObject(TokenClaims.CNF);
+		return cnf == null ? null : cnf.getAsString(TokenClaims.CNF_X5T);
+	}
+
+}
diff --git a/java-security/src/main/java/com/sap/cloud/security/x509/X509CertificateExtractor.java b/java-security/src/main/java/com/sap/cloud/security/x509/X509CertificateExtractor.java
new file mode 100644
index 00000000..7fc1ecb1
--- /dev/null
+++ b/java-security/src/main/java/com/sap/cloud/security/x509/X509CertificateExtractor.java
@@ -0,0 +1,55 @@
+/**
+ * SPDX-FileCopyrightText: 2018-2021 SAP SE or an SAP affiliate company and Cloud Security Client Java contributors
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package com.sap.cloud.security.x509;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nullable;
+import javax.servlet.http.HttpServletRequest;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+import static com.sap.cloud.security.x509.X509Constants.FWD_CLIENT_CERT_HEADER;
+
+/**
+ * X509 certificate extractor.
+ */
+public class X509CertificateExtractor {
+
+	private static final Logger LOGGER = LoggerFactory.getLogger(X509CertificateExtractor.class);
+
+	private X509CertificateExtractor() {
+		// use factory method instead
+	}
+
+	public static X509CertificateExtractor getInstance() {
+		return new X509CertificateExtractor();
+	}
+
+	/**
+	 * Extracts the forwarded client certificate from 'x-forwarded-client-cert'
+	 * header.
+	 *
+	 * @param request
+	 *            the HttpServletRequest
+	 * @return the client certificate object
+	 */
+	@Nullable
+	public X509Certificate getClientCertificate(HttpServletRequest request) {
+		String clientCert = request.getHeader(FWD_CLIENT_CERT_HEADER);
+		LOGGER.debug("{} = {}", FWD_CLIENT_CERT_HEADER, clientCert);
+		if (clientCert != null && !clientCert.isEmpty()) {
+			try {
+				return X509Parser.parseCertificate(clientCert);
+			} catch (CertificateException e) {
+				LOGGER.warn("Could not parse the certificate", e);
+			}
+		}
+		return null;
+	}
+
+}
diff --git a/pom.xml b/pom.xml
index 73bae383..c2f27b86 100644
--- a/pom.xml
+++ b/pom.xml
@@ -60,7 +60,7 @@
 		<!-- make sure that spring core and spring boot versions are compatible-->
 		<spring.boot.version>2.5.6</spring.boot.version>
 		<spring.core.version>5.3.13</spring.core.version>
-		<spring.security.version>5.5.3</spring.security.version>
+		<spring.security.version>5.6.0</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.12</reactor.version>
diff --git a/samples/java-security-usage-ias/manifest.yml b/samples/java-security-usage-ias/manifest.yml
index 4f6865e5..06449c8e 100644
--- a/samples/java-security-usage-ias/manifest.yml
+++ b/samples/java-security-usage-ias/manifest.yml
@@ -10,6 +10,7 @@ applications:
   memory: 896M
   routes:
     - route: java-security-usage-ias-((ID)).((LANDSCAPE_APPS_DOMAIN))
+    - route: java-security-usage-ias-((ID)).cert.((LANDSCAPE_APPS_DOMAIN))
   host: 
   path: target/java-security-usage-ias.war
   buildpacks:
diff --git a/samples/java-security-usage-ias/src/main/java/com/sap/cloud/security/samples/ias/IasSecurityFilter.java b/samples/java-security-usage-ias/src/main/java/com/sap/cloud/security/samples/ias/IasSecurityFilter.java
index f397eb06..889cc0e0 100644
--- a/samples/java-security-usage-ias/src/main/java/com/sap/cloud/security/samples/ias/IasSecurityFilter.java
+++ b/samples/java-security-usage-ias/src/main/java/com/sap/cloud/security/samples/ias/IasSecurityFilter.java
@@ -42,7 +42,7 @@ public class IasSecurityFilter implements Filter {
 				sendUnauthenticatedResponse(response, authenticationResult.getUnauthenticatedReason());
 			}
 		} finally {
-			SecurityContext.clearToken();
+			SecurityContext.clear();
 		}
 	}
 
diff --git a/samples/java-security-usage/src/main/java/com/sap/cloud/security/samples/XsuaaSecurityFilter.java b/samples/java-security-usage/src/main/java/com/sap/cloud/security/samples/XsuaaSecurityFilter.java
index 65c65c92..b8b94af4 100644
--- a/samples/java-security-usage/src/main/java/com/sap/cloud/security/samples/XsuaaSecurityFilter.java
+++ b/samples/java-security-usage/src/main/java/com/sap/cloud/security/samples/XsuaaSecurityFilter.java
@@ -48,7 +48,7 @@ public class XsuaaSecurityFilter implements Filter {
 				sendUnauthenticatedResponse(response, authenticationResult.getUnauthenticatedReason());
 			}
 		} finally {
-			SecurityContext.clearToken();
+			SecurityContext.clear();
 		}
 	}
 
diff --git a/samples/spring-security-hybrid-usage/README.md b/samples/spring-security-hybrid-usage/README.md
index 1495ab2c..8529473d 100644
--- a/samples/spring-security-hybrid-usage/README.md
+++ b/samples/spring-security-hybrid-usage/README.md
@@ -1,5 +1,6 @@
 # Description
 This spring boot application sample uses ```spring-security``` client library to validate jwt tokens issued by ```xsuaa``` service or by ```identity ``` service. On the one hand ```xsuaa``` service issues an access token and on the other hand ```identity``` service issues an oidc token. The tokens vary with regard to the information provided via token claims. In both cases the validated token is available of type [```Token```](https://github.com/SAP/cloud-security-xsuaa-integration/blob/master/java-api/src/main/java/com/sap/cloud/security/token/Token.java) via the ```SecurityContextHolder```.
+This sample also demonstrates usage of `CorrelationIdFilter` that adds correlation_id to MDC context that is used to enhance subsequent/outgoing requests with `X-CorrelationID` header. More information about logging filter library used can be found [here](https://github.com/SAP/cf-java-logging-support/wiki/Instrumenting-Servlets).
 
 # Coding
 This sample is using the [`spring-security`](/spring-security/) library, which is based on [Spring's Security](https://github.com/spring-projects/spring-security) project and runs on SAP Cloud Business Technology Platform. It integrates with [Spring Security OAuth 2.0 Resource Server](https://docs.spring.io/spring-security/site/docs/current/reference/html5/#oauth2resourceserver). The security configuration needs to configure jwt for authentication.
diff --git a/samples/spring-security-hybrid-usage/pom.xml b/samples/spring-security-hybrid-usage/pom.xml
index 76f52ba9..57c95052 100644
--- a/samples/spring-security-hybrid-usage/pom.xml
+++ b/samples/spring-security-hybrid-usage/pom.xml
@@ -52,6 +52,11 @@
 			<artifactId>httpclient</artifactId>
 			<version>${apache.httpclient.version}</version>
 		</dependency>
+		<dependency>
+			<groupId>com.sap.hcp.cf.logging</groupId>
+			<artifactId>cf-java-logging-support-servlet</artifactId>
+			<version>3.5.2</version>
+		</dependency>
 
 		<!-- test dependencies-->
         <dependency>
diff --git a/samples/spring-security-hybrid-usage/src/main/java/sample/spring/security/Application.java b/samples/spring-security-hybrid-usage/src/main/java/sample/spring/security/Application.java
index f69a90fe..93fb741c 100644
--- a/samples/spring-security-hybrid-usage/src/main/java/sample/spring/security/Application.java
+++ b/samples/spring-security-hybrid-usage/src/main/java/sample/spring/security/Application.java
@@ -5,8 +5,14 @@
  */
 package sample.spring.security;
 
+import com.sap.hcp.cf.logging.servlet.filter.RequestLoggingFilter;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.core.Ordered;
+
+import javax.servlet.DispatcherType;
 
 @SpringBootApplication
 public class Application {
@@ -14,4 +20,15 @@ public class Application {
     public static void main(String[] args) {
         SpringApplication.run(Application.class, args);
     }
+
+    @Bean
+    public FilterRegistrationBean<RequestLoggingFilter> loggingFilter() {
+        FilterRegistrationBean<RequestLoggingFilter> registrationBean = new FilterRegistrationBean<>();
+        registrationBean.setFilter(new RequestLoggingFilter());
+        registrationBean.setName("request-logging");
+        registrationBean.addUrlPatterns("/*");
+        registrationBean.setDispatcherTypes(DispatcherType.REQUEST);
+        registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
+        return registrationBean;
+    }
 }
diff --git a/spring-security/src/main/java/com/sap/cloud/security/spring/token/authentication/JavaSecurityContextHolderStrategy.java b/spring-security/src/main/java/com/sap/cloud/security/spring/token/authentication/JavaSecurityContextHolderStrategy.java
index a70813bf..c6f1c1f4 100644
--- a/spring-security/src/main/java/com/sap/cloud/security/spring/token/authentication/JavaSecurityContextHolderStrategy.java
+++ b/spring-security/src/main/java/com/sap/cloud/security/spring/token/authentication/JavaSecurityContextHolderStrategy.java
@@ -46,7 +46,7 @@ public class JavaSecurityContextHolderStrategy implements SecurityContextHolderS
 
 	public void clearContext() {
 		contextHolder.remove();
-		com.sap.cloud.security.token.SecurityContext.clearToken();
+		com.sap.cloud.security.token.SecurityContext.clear();
 	}
 
 	public SecurityContext getContext() {
diff --git a/token-client/pom.xml b/token-client/pom.xml
index 211b4f43..cdb529ff 100644
--- a/token-client/pom.xml
+++ b/token-client/pom.xml
@@ -97,6 +97,11 @@
 			<artifactId>spring-security-oauth2-jose</artifactId>
 			<scope>test</scope>
 		</dependency>
+		<dependency>
+			<groupId>ch.qos.logback</groupId>
+			<artifactId>logback-classic</artifactId>
+			<scope>test</scope>
+		</dependency>
 	</dependencies>
 
 	<build>
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 c567d2b0..804a9d9d 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
@@ -6,6 +6,8 @@
 package com.sap.cloud.security.xsuaa.client;
 
 import com.sap.cloud.security.client.HttpClientFactory;
+import com.sap.cloud.security.servlet.MDCConstants;
+import com.sap.cloud.security.servlet.MDCHelper;
 import com.sap.cloud.security.xsuaa.Assertions;
 import com.sap.cloud.security.xsuaa.http.HttpHeaders;
 import com.sap.cloud.security.xsuaa.tokenflows.TokenCacheConfiguration;
@@ -73,6 +75,7 @@ public class DefaultOAuth2TokenService extends AbstractOAuth2TokenService {
 	@Override
 	protected OAuth2TokenResponse requestAccessToken(URI tokenEndpointUri, HttpHeaders headers,
 			Map<String, String> parameters) throws OAuth2ServiceException {
+		headers.withHeader(MDCConstants.CORRELATION_HEADER, MDCHelper.getOrCreateCorrelationId());
 		HttpPost httpPost = createHttpPost(tokenEndpointUri, headers, parameters);
 		LOGGER.debug("access token request {} - {}", headers, parameters);
 		return executeRequest(httpPost);
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 d8db9f35..ba6b35ba 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
@@ -5,6 +5,8 @@
  */
 package com.sap.cloud.security.xsuaa.client;
 
+import com.sap.cloud.security.servlet.MDCConstants;
+import com.sap.cloud.security.servlet.MDCHelper;
 import com.sap.cloud.security.xsuaa.http.HttpHeaders;
 import com.sap.cloud.security.xsuaa.tokenflows.TokenCacheConfiguration;
 import org.slf4j.Logger;
@@ -69,7 +71,7 @@ public class XsuaaOAuth2TokenService extends AbstractOAuth2TokenService {
 		// Create URI
 		UriComponentsBuilder builder = UriComponentsBuilder.fromUri(tokenEndpointUri);
 		URI requestUri = builder.build().encode().toUri();
-
+		headers.withHeader(MDCConstants.CORRELATION_HEADER, MDCHelper.getOrCreateCorrelationId());
 		org.springframework.http.HttpHeaders springHeaders = new org.springframework.http.HttpHeaders();
 		headers.getHeaders().forEach(h -> springHeaders.add(h.getName(), h.getValue()));
 


Test patch:
diff --git a/java-api/src/test/java/com/sap/cloud/security/x509/X509ParserTest.java b/java-api/src/test/java/com/sap/cloud/security/x509/X509ParserTest.java
new file mode 100644
index 00000000..d7f217af
--- /dev/null
+++ b/java-api/src/test/java/com/sap/cloud/security/x509/X509ParserTest.java
@@ -0,0 +1,83 @@
+/**
+ * SPDX-FileCopyrightText: 2018-2021 SAP SE or an SAP affiliate company and Cloud Security Client Java contributors
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package com.sap.cloud.security.x509;
+
+import org.apache.commons.io.IOUtils;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+
+import static com.sap.cloud.security.x509.X509Parser.getCertificateThumbprint;
+import static com.sap.cloud.security.x509.X509Parser.parseCertificate;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+class X509ParserTest {
+
+	public static final String DN_ISSUER_VALUE = "CN=SAP Cloud Platform Client CA, OU=SAP Cloud Platform Clients, O=SAP SE, L=EU10-Canary, C=DE";
+	private static String x509_base64;
+	private static String x509_pem_format;
+	private static final String x5t = "fU-XoQlhMTpQsz9ArXl6zHIpMGuRO4ExLKdLRTc5VjM";
+
+	@BeforeAll
+	static void beforeAll() throws IOException {
+		x509_base64 = IOUtils.resourceToString("/cf-forwarded-client-cert-base64.txt", StandardCharsets.UTF_8);
+		x509_pem_format = IOUtils.resourceToString("/k8s-forwarded-client-cert-pem.txt", StandardCharsets.UTF_8);
+	}
+
+	@Test
+	void parseCertificate_validBase64() throws CertificateException {
+		assertThat(parseCertificate(x509_base64).getIssuerDN().getName()).isEqualTo(DN_ISSUER_VALUE);
+	}
+
+	@Test
+	void parseCertificate_validPEM() throws CertificateException {
+		assertThat(parseCertificate(x509_pem_format).getIssuerDN().getName()).isEqualTo(DN_ISSUER_VALUE);
+	}
+
+	@Test
+	void parseCertificate_invalidCertificate() {
+		assertThatThrownBy(() -> parseCertificate("R93AEV2m52aX6yXCfzwkL92cW1zBsCuNi82K9PiNmzb/WVB5i7VdXUwAd7bI9ACb"))
+				.isInstanceOf(CertificateException.class);
+		assertThatThrownBy(() -> parseCertificate(""))
+				.isInstanceOf(CertificateException.class);
+		assertThatThrownBy(() -> parseCertificate(null))
+				.isInstanceOf(NullPointerException.class)
+				.hasMessageStartingWith("The provided Certificate can not be null");
+	}
+
+	@Test
+	void formatBase64Cert() {
+		String formattedBase64 = X509Parser.formatBase64Cert(x509_pem_format);
+		assertThat(formattedBase64)
+				.startsWith("-----BEGIN CERTIFICATE-----\n")
+				.endsWith("\n-----END CERTIFICATE-----");
+		assertThat(formattedBase64.chars().filter(ch -> ch == '\n').count()).isEqualTo(5);
+
+		String formattedBase64_2 = X509Parser.formatBase64Cert(x509_base64);
+		assertThat(formattedBase64_2)
+				.startsWith("-----BEGIN CERTIFICATE-----\n")
+				.endsWith("\n-----END CERTIFICATE-----");
+		assertThat(formattedBase64_2.chars().filter(ch -> ch == '\n').count()).isEqualTo(2);
+
+	}
+
+	@Test
+	void getX509ThumbprintTest() throws NoSuchAlgorithmException, CertificateException {
+		assertThat(getCertificateThumbprint(parseCertificate(x509_base64))).isEqualTo(x5t);
+	}
+
+	@Test
+	void testRegex() {
+		String testCase = "I\nDon't\nWant\r\nTo Be\r On New Line";
+		String result = testCase.replaceAll("\\s", "");
+		assertThat(result).isEqualTo("IDon'tWantToBeOnNewLine");
+	}
+}
\ No newline at end of file
diff --git a/java-api/src/test/resources/cf-forwarded-client-cert-base64.txt b/java-api/src/test/resources/cf-forwarded-client-cert-base64.txt
new file mode 100644
index 00000000..55aa9e11
--- /dev/null
+++ b/java-api/src/test/resources/cf-forwarded-client-cert-base64.txt
@@ -0,0 +1,1 @@
+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
\ No newline at end of file
diff --git a/java-api/src/test/resources/k8s-forwarded-client-cert-pem.txt b/java-api/src/test/resources/k8s-forwarded-client-cert-pem.txt
new file mode 100644
index 00000000..0c3f9569
--- /dev/null
+++ b/java-api/src/test/resources/k8s-forwarded-client-cert-pem.txt
@@ -0,0 +1,1 @@
+-----BEGIN CERTIFICATE-----\nMIIGIDCCBAigAwIBAgIRANaAYWrL++TtzQfGqb8UZiMwDQYJKoZIhvcNAQELBQAw\ngYAxCzAJBgNVBAYTAkRFMRQwEgYDVQQHDAtFVTEwLUNhbmFyeTEPMA0GA1UECgwG\nU0FQIFNFMSMwIQYDVQQLDBpTQVAgQ2xvdWQgUGxhdGZvcm0gQ2xpZW50czElMCMG\nA1UEAwwcU0FQIENsb3VkIFBsYXRmb3JtIENsaWVudCBDQTAeFw0yMTEwMjIwNjE3\nMzlaFw0yMTExMjEwNzE3MzlaMIIBBDELMAkGA1UEBhMCREUxDzANBgNVBAoTBlNB\nUCBTRTEjMCEGA1UECxMaU0FQIENsb3VkIFBsYXRmb3JtIENsaWVudHMxDzANBgNV\nBAsTBkNhbmFyeTEtMCsGA1UECxMkOGUxYWZmYjItNjJhMS00M2NjLWE2ODctMmJh\nNzVlNGIzZDg0MSswKQYDVQQHEyJhb3hrMmFkZGguYWNjb3VudHM0MDAub25kZW1h\nbmQuY29tMVIwUAYDVQQDE0k3YWE3OTVlOS03NDRmLTRmM2EtOGU2My1iMjA3NmUz\nNTQ4NmEvZmU5NDYwOGUtOGVmYy00NWYxLWE3YmUtZTU3MmJlYTUyNGQ4MIIBIjAN\nBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAj6qJvhloGp14Z+Xzjurn/mdX0b3A\nNrrbf8GF3kH96qC1fPqcgjhE8U2obsRnfeD4yZ5mIKookVxuf6kzDzV1tmPMpH+b\n1eIQTyx3PLNG0x1mOX9LNbg6AnvsCTL5Kk6SRJ/CctmxdaEsO1WwVrouEQnJhx8G\nxSMmXmSCk8MelWAyoFHg+WzkrmELAZASo6mVC7CIdlRuh99mZjEfQZn3UOPEoyAr\nCxp2eOkY3l+5gDpv6wbZzsXy3AT9YXBpKWTmxZwTFJDPYEFBGyorRbAiaeqoREXV\nrsxn6HJMgz42cXSTv9N3jtgUqcrPDJWtoZu4r5OxsCX5iYHdiQtn2U9gpwIDAQAB\no4IBDDCCAQgwCQYDVR0TBAIwADAfBgNVHSMEGDAWgBTMIIRbhxWLN/l+h7HJPsNv\nlQRukTAdBgNVHQ4EFgQUtPJ3YRqLUdqDBDTMFeKpQeD+QSwwDgYDVR0PAQH/BAQD\nAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMCMIGVBgNVHR8EgY0wgYowgYeggYSggYGG\nf2h0dHA6Ly9zYXAtY2xvdWQtcGxhdGZvcm0tY2xpZW50LWNhLWV1MTAtY2FuYXJ5\nLWNybHMuczMuZXUtY2VudHJhbC0xLmFtYXpvbmF3cy5jb20vY3JsL2Y4YWJlNTRl\nLTE5NTEtNDcwZS1hZTJkLWRlNDBjMTYzMzQxYy5jcmwwDQYJKoZIhvcNAQELBQAD\nggIBADZrhqZ6wKwrJTaew+zk4OLrPlMyUs7YQqe0h2g5JhAnnRVK2/7G+wCeOrGj\nl8adIxY+b+j8npWM8P/LktQF/yRD5ZAHQA0caL4SUpBKnCmmoz1xfaxge05UrGlF\nkdTtu44IY+7rZChh0oxE/U8fhrdJ8FFawf7WXzl9czzVfcbQgiNy+mphifeMexLN\nVqIxMhUu6idNq5zs+fcGUhVcQHswlDIwZOofzFe2tKf5l5H3clzDGTMpLiRwlTyT\naGmqkoH2KjknGkwS3C8mjqCwlu51sOPkh9b+q0uO7uZygMYvRvZnYcr6j38a1a/b\npbhgarZLR3etQ+3/m7Yp7zPho/u8AvNor0NCo9xok9ykU2YT9WCtrUx4i4cH3Q3K\nVDo8TAtkQ9wK9okLuHQTsU0eyRanHmO3eA4a2afMdgEruCD+PYejKvx7v6jM1IF8\nnoC/Sc67qB6NXasmQMwUsXHCOBBJn1p99Wn9J/NGtmptWvOIaC8lGtvo1Qfu2GhM\nArD0aAE6S1niPqZzXcxwzaqz4YH4CitAsG0P6QvX/EEiu9rA4yl2P8cVwty49q6r\np1dGrc0AdOIXsVL/SsvtJS/mMv7/XaZXxjaLdfCcHzVU88DS/nqsrfd2Qwa+6268\nQxOGYoBpStnOQNPDI8tbfSesuz8+xKcbLI6pS4Ts+mZ59pKW\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\n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\n2JX4S0blcGoU+np5\n-----END CERTIFICATE-----
\ No newline at end of file
diff --git a/java-security-test/src/main/java/com/sap/cloud/security/test/JwtGenerator.java b/java-security-test/src/main/java/com/sap/cloud/security/test/JwtGenerator.java
index b8f744d6..142bf798 100644
--- a/java-security-test/src/main/java/com/sap/cloud/security/test/JwtGenerator.java
+++ b/java-security-test/src/main/java/com/sap/cloud/security/test/JwtGenerator.java
@@ -220,6 +220,28 @@ public class JwtGenerator {
 		return this;
 	}
 
+	/**
+	 * Sets the claims with the given names to the given string value. Note: for
+	 * overwriting client Id claim, "azp" claim value should be overwritten instead
+	 * of deprecated "cid"
+	 *
+	 * @param claimName
+	 *            the name of the claim to be set.
+	 * @param map
+	 *            map of key value pairs of claims to be set.
+	 * @return the builder object.
+	 * @throws JsonParsingException
+	 *             if the given object does not contain valid json.
+	 */
+	public JwtGenerator withClaimValue(String claimName, Map<String, String> map) {
+		try {
+			jsonPayload.put(claimName, map);
+		} catch (JSONException e) {
+			throw new JsonParsingException(e.getMessage());
+		}
+		return this;
+	}
+
 	/**
 	 * Sets the claim with the given name to the given string values. Note: for
 	 * overwriting client Id claim, "azp" claim value should be overwritten instead
diff --git a/java-security/src/test/java/com/sap/cloud/security/adapter/spring/SAPOfflineTokenServicesCloudTest.java b/java-security/src/test/java/com/sap/cloud/security/adapter/spring/SAPOfflineTokenServicesCloudTest.java
index 7cd78559..6e62c017 100644
--- a/java-security/src/test/java/com/sap/cloud/security/adapter/spring/SAPOfflineTokenServicesCloudTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/adapter/spring/SAPOfflineTokenServicesCloudTest.java
@@ -63,7 +63,7 @@ public class SAPOfflineTokenServicesCloudTest {
 				.build();
 
 		cut = new SAPOfflineTokenServicesCloud(configuration, jwtValidatorBuilderSpy);
-		SecurityContext.clearToken();
+		SecurityContext.clear();
 	}
 
 	@Test
diff --git a/java-security/src/test/java/com/sap/cloud/security/servlet/IasTokenAuthenticatorTest.java b/java-security/src/test/java/com/sap/cloud/security/servlet/IasTokenAuthenticatorTest.java
index a9083521..fb1e3172 100644
--- a/java-security/src/test/java/com/sap/cloud/security/servlet/IasTokenAuthenticatorTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/servlet/IasTokenAuthenticatorTest.java
@@ -8,7 +8,6 @@ package com.sap.cloud.security.servlet;
 import com.sap.cloud.security.config.OAuth2ServiceConfiguration;
 import com.sap.cloud.security.config.OAuth2ServiceConfigurationBuilder;
 import com.sap.cloud.security.config.Service;
-import com.sap.cloud.security.config.cf.CFConstants;
 import com.sap.cloud.security.token.SapIdToken;
 import com.sap.cloud.security.token.SecurityContext;
 import com.sap.cloud.security.token.validation.ValidationListener;
@@ -36,7 +35,7 @@ public class IasTokenAuthenticatorTest {
 
 	private final static HttpServletResponse HTTP_RESPONSE = Mockito.mock(HttpServletResponse.class);
 
-	private SapIdToken token;
+	private final SapIdToken token;
 
 	private AbstractTokenAuthenticator cut;
 
diff --git a/java-security/src/test/java/com/sap/cloud/security/servlet/IasTokenAuthenticatorX509Test.java b/java-security/src/test/java/com/sap/cloud/security/servlet/IasTokenAuthenticatorX509Test.java
new file mode 100644
index 00000000..41c86404
--- /dev/null
+++ b/java-security/src/test/java/com/sap/cloud/security/servlet/IasTokenAuthenticatorX509Test.java
@@ -0,0 +1,135 @@
+/**
+ * SPDX-FileCopyrightText: 2018-2021 SAP SE or an SAP affiliate company and Cloud Security Client Java contributors
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package com.sap.cloud.security.servlet;
+
+import com.sap.cloud.security.config.OAuth2ServiceConfiguration;
+import com.sap.cloud.security.config.OAuth2ServiceConfigurationBuilder;
+import com.sap.cloud.security.config.Service;
+import com.sap.cloud.security.token.SapIdToken;
+import com.sap.cloud.security.token.validation.validators.JwtValidatorBuilder;
+import com.sap.cloud.security.token.validation.validators.JwtX5tValidator;
+import com.sap.cloud.security.util.HttpClientTestFactory;
+import com.sap.cloud.security.xsuaa.http.HttpHeaders;
+import org.apache.commons.io.IOUtils;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+import static com.sap.cloud.security.x509.X509Constants.FWD_CLIENT_CERT_HEADER;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+class IasTokenAuthenticatorX509Test {
+
+	private final static HttpServletResponse HTTP_RESPONSE = Mockito.mock(HttpServletResponse.class);
+
+	private final SapIdToken tokenValidX5t;
+	private final SapIdToken tokenInvalidX5t;
+	private final String x509;
+
+	private static AbstractTokenAuthenticator cut;
+
+	public IasTokenAuthenticatorX509Test() throws IOException {
+		tokenValidX5t = new SapIdToken(IOUtils.resourceToString("/iasTokenWithCnfRSA256.txt", UTF_8));
+		tokenInvalidX5t = new SapIdToken(IOUtils.resourceToString("/iasTokenInvalidCnfRSA256.txt", UTF_8));
+		x509 = IOUtils.resourceToString("/cf-forwarded-client-cert.txt", UTF_8);
+	}
+
+	@BeforeAll
+	static void beforeAll() throws IOException {
+		OAuth2ServiceConfiguration configuration = OAuth2ServiceConfigurationBuilder
+				.forService(Service.IAS)
+				.withDomains("myauth.com")
+				.withClientId("T000310")
+				.build();
+
+		CloseableHttpClient httpClientMock = Mockito.mock(CloseableHttpClient.class);
+
+		CloseableHttpResponse oidcResponse = HttpClientTestFactory
+				.createHttpResponse("{\"jwks_uri\" : \"https://application.auth.com/oauth2/certs\"}");
+		CloseableHttpResponse tokenKeysResponse = HttpClientTestFactory
+				.createHttpResponse(IOUtils.resourceToString("/iasJsonWebTokenKeys.json", UTF_8));
+		when(httpClientMock.execute(any(HttpGet.class)))
+				.thenReturn(oidcResponse)
+				.thenReturn(tokenKeysResponse);
+
+		JwtValidatorBuilder
+				.getInstance(configuration)
+				.with(new JwtX5tValidator());
+		cut = new IasTokenAuthenticator()
+				.withServiceConfiguration(configuration)
+				.withHttpClient(httpClientMock);
+	}
+
+	@Test
+	void validateRequest_validTokenValidX5t_noCertificate() {
+		HttpServletRequest httpRequest = createRequestWithToken(tokenValidX5t.getTokenValue());
+
+		TokenAuthenticationResult response = cut.validateRequest(httpRequest, HTTP_RESPONSE);
+
+		assertThat(response.getUnauthenticatedReason())
+				.contains("Error during token validation: Certificate validation failed");
+		assertThat(response.isAuthenticated()).isFalse();
+
+		when(httpRequest.getHeader(FWD_CLIENT_CERT_HEADER)).thenReturn("");
+		TokenAuthenticationResult response2 = cut.validateRequest(httpRequest, HTTP_RESPONSE);
+
+		assertThat(response2.getUnauthenticatedReason())
+				.contains("Error during token validation: Certificate validation failed");
+		assertThat(response2.isAuthenticated()).isFalse();
+
+	}
+
+	@Test
+	void validateRequest_validTokenValidX5t_invalidCertificate() {
+		HttpServletRequest httpRequest = createRequestWithToken(tokenValidX5t.getTokenValue());
+		when(httpRequest.getHeader(FWD_CLIENT_CERT_HEADER)).thenReturn("INVALIDx509");
+
+		TokenAuthenticationResult response = cut.validateRequest(httpRequest, HTTP_RESPONSE);
+
+		assertThat(response.getUnauthenticatedReason())
+				.contains("Error during token validation: Certificate validation failed");
+		assertThat(response.isAuthenticated()).isFalse();
+	}
+
+	@Test
+	void validateRequest_validTokenInvalidX5t_withCertificate() {
+		HttpServletRequest httpRequest = createRequestWithToken(tokenInvalidX5t.getTokenValue());
+		when(httpRequest.getHeader(FWD_CLIENT_CERT_HEADER)).thenReturn(x509);
+
+		TokenAuthenticationResult response = cut.validateRequest(httpRequest, HTTP_RESPONSE);
+
+		assertThat(response.getUnauthenticatedReason())
+				.contains("Error during token validation: Certificate validation failed");
+		assertThat(response.isAuthenticated()).isFalse();
+	}
+
+	@Test
+	void validateRequest_validTokenWithCnf_withCertificate() {
+		HttpServletRequest httpRequest = createRequestWithToken(tokenValidX5t.getTokenValue());
+		when(httpRequest.getHeader(FWD_CLIENT_CERT_HEADER)).thenReturn(x509);
+
+		TokenAuthenticationResult response = cut.validateRequest(httpRequest, HTTP_RESPONSE);
+
+		assertThat(response.getUnauthenticatedReason()).isEmpty();
+		assertThat(response.isAuthenticated()).isTrue();
+	}
+
+	private HttpServletRequest createRequestWithToken(String bearerAuthorizationHeader) {
+		HttpServletRequest httpRequest = Mockito.mock(HttpServletRequest.class);
+		when(httpRequest.getHeader(HttpHeaders.AUTHORIZATION)).thenReturn("Bearer " + bearerAuthorizationHeader);
+		return httpRequest;
+	}
+}
diff --git a/java-security/src/test/java/com/sap/cloud/security/token/SapIdTokenTest.java b/java-security/src/test/java/com/sap/cloud/security/token/SapIdTokenTest.java
index a10b6c33..5267bdb8 100644
--- a/java-security/src/test/java/com/sap/cloud/security/token/SapIdTokenTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/token/SapIdTokenTest.java
@@ -54,7 +54,7 @@ public class SapIdTokenTest {
 	@Test
 	public void getCnfThumbprint() {
 		assertThat(cut.getCnfX509Thumbprint()).isNull();
-		assertThat(cut2.getCnfX509Thumbprint()).isEqualTo("2blWiJDH07q0b2qEwShOIxtt10CkZ5xdDw4Vbs8ddoI");
+		assertThat(cut2.getCnfX509Thumbprint()).isEqualTo("fU-XoQlhMTpQsz9ArXl6zHIpMGuRO4ExLKdLRTc5VjM");
 	}
 
 }
\ No newline at end of file
diff --git a/java-security/src/test/java/com/sap/cloud/security/token/SecurityContextTest.java b/java-security/src/test/java/com/sap/cloud/security/token/SecurityContextTest.java
index ab691c73..e2d881fd 100644
--- a/java-security/src/test/java/com/sap/cloud/security/token/SecurityContextTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/token/SecurityContextTest.java
@@ -25,7 +25,7 @@ public class SecurityContextTest {
 
 	@Before
 	public void setUp() {
-		SecurityContext.clearToken();
+		SecurityContext.clear();
 	}
 
 	@Test
@@ -51,7 +51,7 @@ public class SecurityContextTest {
 	@Test
 	public void clear_removesToken() {
 		SecurityContext.setToken(TOKEN);
-		SecurityContext.clearToken();
+		SecurityContext.clear();
 
 		assertThat(SecurityContext.getToken()).isNull();
 	}
@@ -70,7 +70,7 @@ public class SecurityContextTest {
 			throws ExecutionException, InterruptedException {
 		SecurityContext.setToken(TOKEN);
 
-		executorService.submit(() -> SecurityContext.clearToken()).get(); // run and await other thread
+		executorService.submit(() -> SecurityContext.clear()).get(); // run and await other thread
 
 		assertThat(SecurityContext.getToken()).isEqualTo(TOKEN);
 	}
diff --git a/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/JwtX5tValidatorTest.java b/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/JwtX5tValidatorTest.java
new file mode 100644
index 00000000..08257240
--- /dev/null
+++ b/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/JwtX5tValidatorTest.java
@@ -0,0 +1,107 @@
+/**
+ * SPDX-FileCopyrightText: 2018-2021 SAP SE or an SAP affiliate company and Cloud Security Client Java contributors
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package com.sap.cloud.security.token.validation.validators;
+
+import com.sap.cloud.security.token.SecurityContext;
+import com.sap.cloud.security.token.Token;
+import com.sap.cloud.security.token.validation.ValidationResult;
+import com.sap.cloud.security.x509.X509Parser;
+import org.apache.commons.io.IOUtils;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+import org.mockito.internal.util.collections.Sets;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Collections;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class JwtX5tValidatorTest {
+
+	private static final JwtX5tValidator CUT = new JwtX5tValidator();
+	public static final String X5T_VALIDATOR_DISABLED = "X5tValidator is not enabled";
+	private static String tokenWithX5t;
+	private static String tokenWithInvalidX5t;
+	private static final Token TOKEN = Mockito.mock(Token.class);
+	private static X509Certificate x509;
+	private static final String INVALID_MESSAGE = "Certificate validation failed";
+
+	@BeforeAll
+	static void beforeAll() throws IOException, CertificateException {
+		tokenWithX5t = IOUtils.resourceToString("/iasTokenWithCnfRSA256.txt", StandardCharsets.UTF_8);
+		tokenWithInvalidX5t = IOUtils.resourceToString("/iasTokenInvalidCnfRSA256.txt", StandardCharsets.UTF_8);
+		x509 = X509Parser
+				.parseCertificate(IOUtils.resourceToString("/cf-forwarded-client-cert.txt", StandardCharsets.UTF_8));
+	}
+
+	@Test
+	void validateToken_WithValidCnf_validX509() {
+		Token token = Token.create(tokenWithX5t);
+		SecurityContext.setClientCertificate(x509);
+		ValidationResult result = CUT.validate(token);
+		assertTrue(result.isValid());
+	}
+
+	@Test
+	void validateToken_WithInvalidCnf_validX509() {
+		Token token = Token.create(tokenWithInvalidX5t);
+		SecurityContext.setClientCertificate(x509);
+		ValidationResult result = CUT.validate(token);
+		assertTrue(result.isErroneous());
+		assertEquals(INVALID_MESSAGE, result.getErrorDescription());
+	}
+
+	@Test
+	void validateToken_WithInvalidCnf_invalidX509() {
+		Token token = Token.create(tokenWithInvalidX5t);
+		SecurityContext.setClientCertificate(null);
+		ValidationResult result = CUT.validate(token);
+		assertTrue(result.isErroneous());
+		assertEquals(INVALID_MESSAGE, result.getErrorDescription());
+	}
+
+	@Disabled("until proofOfPossesion validator subchain is implemented")
+	@Test
+	void validateToken_validatorDisabled() {
+		Token token = Token.create(tokenWithX5t);
+		ValidationResult result = CUT.validate(token);
+		assertTrue(result.isErroneous());
+		assertEquals(X5T_VALIDATOR_DISABLED, result.getErrorDescription());
+	}
+
+	@Disabled("until aud claim validation is clarified")
+	@Test
+	void validateToken_NoCnfSingleAud() {
+		Mockito.when(TOKEN.getAudiences()).thenReturn(Sets.newSet("myClientId"));
+		ValidationResult result = CUT.validate(TOKEN);
+		assertTrue(result.isValid());
+	}
+
+	@Disabled("until aud claim validation is clarified")
+	@Test
+	void validateToken_NoCnfMultipleAud() {
+		Mockito.when(TOKEN.getAudiences()).thenReturn(Sets.newSet("aud1", "aud2"));
+		ValidationResult result = CUT.validate(TOKEN);
+		assertTrue(result.isErroneous());
+		assertEquals(INVALID_MESSAGE, result.getErrorDescription());
+	}
+
+	@Disabled("until aud claim validation is clarified")
+	@Test
+	void validateToken_NoCnfEmptyAud() {
+		Mockito.when(TOKEN.getAudiences()).thenReturn(Collections.emptySet());
+		ValidationResult result = CUT.validate(TOKEN);
+		assertTrue(result.isErroneous());
+		assertEquals(INVALID_MESSAGE, result.getErrorDescription());
+	}
+
+}
\ No newline at end of file
diff --git a/java-security/src/test/resources/cf-forwarded-client-cert.txt b/java-security/src/test/resources/cf-forwarded-client-cert.txt
new file mode 100644
index 00000000..55aa9e11
--- /dev/null
+++ b/java-security/src/test/resources/cf-forwarded-client-cert.txt
@@ -0,0 +1,1 @@
+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
\ No newline at end of file
diff --git a/java-security/src/test/resources/iasTokenInvalidCnfRSA256.txt b/java-security/src/test/resources/iasTokenInvalidCnfRSA256.txt
new file mode 100644
index 00000000..f5096883
--- /dev/null
+++ b/java-security/src/test/resources/iasTokenInvalidCnfRSA256.txt
@@ -0,0 +1,1 @@
+eyJraWQiOiJkZWZhdWx0LWtpZC1pYXMiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOlsiYXVkMiIsIlQwMDAzMTAiXSwidXNlcl91dWlkIjoidGhlLXVzZXItaWQiLCJhenAiOiJUMDAwMzEwIiwiaXNzIjoiaHR0cHM6Ly9hcHBsaWNhdGlvbi5teWF1dGguY29tIiwiem9uZV91dWlkIjoidGhlLXpvbmUtaWQiLCJjbmYiOnsieDV0I1MyNTYiOiJpbnZhbGlkIn0sImV4cCI6Njk3NDAzMTYwMCwiY2lkIjoiVDAwMDMxMCJ9.DmBD0J2pN6ap6gxajLVfSvdf8mPpDkJtmggbLjgRMAWT2mtJtTjlUJ9a0vZltzNXNdwxC9R_YP05K5Pkzy5a4GOWNMuptlggF_oDaOIlQ-Bz7K-V6181hAssg6S_kD6i_K4cM4obu7KEGhNbVc89RRCHg1FF_S0HPVXW-RJbVcnPG44tN-n-4PiQ0dZURiqwm-HcLRTm_MT6WLJIvT48A0qtKUlVDlYqOYizWzRTnow7IFac2LEp7Z7nDwkuGwfaUaW88AiV3J-16c9IHEJ7n_B8R7aens5rv2WinV_kaTk5N4I2xI0bFYavEgjszVFSRPj0eVimHm0d2McucUwA7Q
\ No newline at end of file
diff --git a/java-security/src/test/resources/iasTokenWithCnfRSA256.txt b/java-security/src/test/resources/iasTokenWithCnfRSA256.txt
index d5106567..d9f15531 100644
--- a/java-security/src/test/resources/iasTokenWithCnfRSA256.txt
+++ b/java-security/src/test/resources/iasTokenWithCnfRSA256.txt
@@ -1,1 +1,1 @@
-eyJraWQiOiJkZWZhdWx0LWtpZC1pYXMiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiJzYi1jbGllbnRJZCF0MDgxNSIsInVzZXJfdXVpZCI6InRoZS11c2VyLWlkIiwiYXpwIjoic2ItY2xpZW50SWQhdDA4MTUiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjYzMjIxIiwiem9uZV91dWlkIjoidGhlLXpvbmUtaWQiLCJncm91cHMiOlsiSUFTQVVUSFpfUmVhZCJdLCJjbmYiOnsieDV0I1MyNTYiOiIyYmxXaUpESDA3cTBiMnFFd1NoT0l4dHQxMENrWjV4ZER3NFZiczhkZG9JIn0sImV4cCI6Njk3NDAzMTYwMCwiY2lkIjoic2ItY2xpZW50SWQhdDA4MTUifQ.WB3uj8dxwfMPceftjo9gztyfVZc04EFiNuzirYfzSdOQgjo7NEFvzC9QF4CRlPOmMcx8aWzTpBfOOKeaAqIIc97cxLRsw08BnkiTEIB5TopI8sDNxQZRs0h2WLBfq8sxQM-i2A6dkrePi9qw_lzzM38f1o8l2gIQgQuhHiKwbSOx3bHh6Fozpu8bb8DDO_eoE9wjI5bbk9A6rUEo-E6Se2RBdxKjKtREaMEz1auOLxRxtNiDYLfP_OyUvitMblQqva7mrI2blU9dVwdm3LCp3JgkdolUy5BEG24Ub6AABwwsrLNFM2bcaU12fHiG6hzOtuzXqPAmHuo2jkoxzS0P8w
\ No newline at end of file
+eyJraWQiOiJkZWZhdWx0LWtpZC1pYXMiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOlsiYXVkMiIsIlQwMDAzMTAiXSwidXNlcl91dWlkIjoidGhlLXVzZXItaWQiLCJhenAiOiJUMDAwMzEwIiwiaXNzIjoiaHR0cHM6Ly9hcHBsaWNhdGlvbi5teWF1dGguY29tIiwiem9uZV91dWlkIjoidGhlLXpvbmUtaWQiLCJjbmYiOnsieDV0I1MyNTYiOiJmVS1Yb1FsaE1UcFFzejlBclhsNnpISXBNR3VSTzRFeExLZExSVGM1VmpNIn0sImV4cCI6Njk3NDAzMTYwMCwiY2lkIjoiVDAwMDMxMCJ9.fuFVYzTgfagkXZHtPV2NivAge9n7yp56fQXuthvKym6quAC3cgUVpZRf0eg_8c2dRmb89oKC6F1GyD1qVpACTIu8zfHepdP_fqis7zEGXc5cjfCsy0dnpAOHkuJnh_AlmuiOkR-bnJZLP81UKVwVPrx57eQD1xT-rzJCnClyUqG-_TJHOXYqEtlnLYYdnjvbwpGCudikAflVAeyFV81FHGmMSt1dAC7HIUoP0AWyNUOmvhe3PzK0pBKoL15jdgxHZhYptJE8MJmspFzXRYEj0sw0PcWW9mz0igZOFp4BWNNDNOouGQCUW8OsQ4ZJKgPam94MCKQgypvHCFVflU7gxA
\ No newline at end of file
diff --git a/samples/java-security-usage-ias/src/test/java/com/sap/cloud/security/samples/ias/HelloJavaServletIntegrationTest.java b/samples/java-security-usage-ias/src/test/java/com/sap/cloud/security/samples/ias/HelloJavaServletIntegrationTest.java
index 2d7270b6..22b20aac 100644
--- a/samples/java-security-usage-ias/src/test/java/com/sap/cloud/security/samples/ias/HelloJavaServletIntegrationTest.java
+++ b/samples/java-security-usage-ias/src/test/java/com/sap/cloud/security/samples/ias/HelloJavaServletIntegrationTest.java
@@ -33,7 +33,7 @@ public class HelloJavaServletIntegrationTest {
 
 	@After
 	public void tearDown() {
-		SecurityContext.clearToken();
+		SecurityContext.clear();
 	}
 
 	@Test
diff --git a/samples/java-security-usage/src/test/java/com/sap/cloud/security/samples/HelloJavaServletIntegrationTest.java b/samples/java-security-usage/src/test/java/com/sap/cloud/security/samples/HelloJavaServletIntegrationTest.java
index c07fd224..8bde06cd 100644
--- a/samples/java-security-usage/src/test/java/com/sap/cloud/security/samples/HelloJavaServletIntegrationTest.java
+++ b/samples/java-security-usage/src/test/java/com/sap/cloud/security/samples/HelloJavaServletIntegrationTest.java
@@ -36,7 +36,7 @@ public class HelloJavaServletIntegrationTest {
 
 	@AfterEach
 	public void tearDown() {
-		SecurityContext.clearToken();
+		SecurityContext.clear();
 	}
 
 	@Test
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 1b82121b..52717287 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
@@ -5,6 +5,11 @@
  */
 package com.sap.cloud.security.xsuaa.client;
 
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.read.ListAppender;
+import com.sap.cloud.security.servlet.MDCHelper;
 import com.sap.cloud.security.xsuaa.http.HttpHeaders;
 import com.sap.cloud.security.xsuaa.http.HttpHeadersFactory;
 import com.sap.cloud.security.xsuaa.util.HttpClientTestFactory;
@@ -22,6 +27,8 @@ import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 import org.mockito.junit.MockitoJUnitRunner;
+import org.slf4j.LoggerFactory;
+import org.slf4j.MDC;
 
 import java.io.IOException;
 import java.net.URI;
@@ -29,6 +36,7 @@ import java.nio.charset.StandardCharsets;
 import java.time.Instant;
 import java.util.Map;
 
+import static com.sap.cloud.security.servlet.MDCConstants.CORRELATION_ID;
 import static java.util.Collections.*;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -78,6 +86,27 @@ public class DefaultOAuth2TokenServiceTest {
 		assertThat(re.getTokenType()).isEqualTo(TOKEN_TYPE);
 	}
 
+	@Test
+	public void correlationIdProvisioning() throws IOException {
+		CloseableHttpResponse response = HttpClientTestFactory.createHttpResponse(VALID_JSON_RESPONSE);
+		when(mockHttpClient.execute(any(HttpPost.class))).thenReturn(response);
+
+		ListAppender<ILoggingEvent> listAppender = new ListAppender<>();
+		Logger logger = (Logger) LoggerFactory.getLogger(MDCHelper.class);
+		listAppender.start();
+		logger.addAppender(listAppender);
+
+		requestAccessToken(emptyMap());
+		assertThat(listAppender.list.get(0).getLevel()).isEqualTo(Level.INFO);
+		assertThat(listAppender.list.get(0).getMessage()).contains("was not found in the MDC");
+
+		MDC.put(CORRELATION_ID, "my-correlation-id");
+		requestAccessToken(emptyMap());
+		assertThat(listAppender.list.get(1).getLevel()).isEqualTo(Level.DEBUG);
+		assertThat(listAppender.list.get(1).getArgumentArray()[1]).isEqualTo(("my-correlation-id"));
+		MDC.clear();
+	}
+
 	@Test
 	public void executeWithAdditionalParameters_putsParametersIntoPostBody() throws IOException {
 		ArgumentCaptor<HttpPost> httpPostCaptor = ArgumentCaptor.forClass(HttpPost.class);
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 d0c2b184..3b37beb0 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,32 +5,42 @@
  */
 package com.sap.cloud.security.xsuaa.client;
 
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.read.ListAppender;
 import com.sap.cloud.security.config.ClientCredentials;
 import com.sap.cloud.security.config.ClientIdentity;
+import com.sap.cloud.security.servlet.MDCHelper;
+import org.assertj.core.api.Assertions;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.junit.MockitoJUnitRunner;
+import org.slf4j.LoggerFactory;
+import org.slf4j.MDC;
 import org.springframework.http.HttpEntity;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.client.HttpClientErrorException;
 import org.springframework.web.client.RestOperations;
 
+import java.io.IOException;
 import java.net.URI;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 
+import static com.sap.cloud.security.servlet.MDCConstants.CORRELATION_ID;
 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;
+import static org.mockito.Mockito.*;
 
 @RunWith(MockitoJUnitRunner.class)
 public class XsuaaOAuth2TokenServiceClientCredentialsTest {
@@ -145,4 +155,26 @@ public class XsuaaOAuth2TokenServiceClientCredentialsTest {
 		assertThat(accessToken.getAccessToken(), is(responseMap.get(ACCESS_TOKEN)));
 	}
 
+	@Test
+	public void correlationIdProvisioning() throws IOException {
+		when(mockRestOperations.postForEntity(any(), any(), any())).thenReturn(new ResponseEntity<>(responseMap, HttpStatus.OK));
+
+		ListAppender<ILoggingEvent> listAppender = new ListAppender<>();
+		Logger logger = (Logger) LoggerFactory.getLogger(MDCHelper.class);
+		listAppender.start();
+		logger.addAppender(listAppender);
+
+		cut.retrieveAccessTokenViaClientCredentialsGrant(tokenEndpoint,
+				clientIdentity, null, null, Collections.emptyMap(), false);
+		Assertions.assertThat(listAppender.list.get(0).getLevel()).isEqualTo(Level.INFO);
+		Assertions.assertThat(listAppender.list.get(0).getMessage()).contains("was not found in the MDC");
+
+		MDC.put(CORRELATION_ID, "my-correlation-id");
+		cut.retrieveAccessTokenViaClientCredentialsGrant(tokenEndpoint,
+				clientIdentity, null, null, Collections.emptyMap(), false);
+		Assertions.assertThat(listAppender.list.get(1).getLevel()).isEqualTo(Level.DEBUG);
+		Assertions.assertThat(listAppender.list.get(1).getArgumentArray()[1]).isEqualTo(("my-correlation-id"));
+		MDC.clear();
+	}
+
 }
\ No newline at end of file