Repository: SAP/cloud-security-xsuaa-integration
Pull request #205 (2020-01-14 18:28:23)

Enhancements for IAS service
- enhancements in java-security and java-security-test for parsing environment
- by default not supported: service name needs to be configured


Patch:
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 e8cad98c..7ea437aa 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
@@ -30,8 +30,13 @@ public interface Environment {
 	@Nullable
 	OAuth2ServiceConfiguration getXsuaaConfiguration();
 
-	// @Nullable
-	// OAuth2ServiceConfiguration getIasConfiguration(); // TODO IAS
+	/**
+	 * Return OAuth service configuration of IAS identity service instance.
+	 *
+	 * @return the OAuth service configuration or null, in case there is no instance
+	 */
+	@Nullable
+	OAuth2ServiceConfiguration getIasConfiguration();
 
 	/**
 	 * Returns number of Xsuaa identity service instances.
diff --git a/java-api/src/main/java/com/sap/cloud/security/config/Service.java b/java-api/src/main/java/com/sap/cloud/security/config/Service.java
index 3ccde924..ea001812 100644
--- a/java-api/src/main/java/com/sap/cloud/security/config/Service.java
+++ b/java-api/src/main/java/com/sap/cloud/security/config/Service.java
@@ -1,10 +1,19 @@
 package com.sap.cloud.security.config;
 
+import org.slf4j.LoggerFactory;
+
 /**
  * Represents a supported identity service.
  */
 public enum Service {
-	XSUAA("xsuaa"), IAS("identity-beta");
+
+	XSUAA("xsuaa"), IAS(getIasServiceName());
+
+	private static String getIasServiceName() {
+		String iasServiceName = System.getenv("IAS_SERVICE_NAME"); // TODO as of now its "identity-beta"
+		LoggerFactory.getLogger(Service.class).error("IAS Service is not yet supported!!!");
+		return iasServiceName;
+	}
 
 	private final String cloudFoundryName;
 
@@ -19,6 +28,9 @@ public enum Service {
 	 * @return name of the identity service in context of Cloud Foundry environment.
 	 */
 	public String getCFName() {
+		if(this == IAS && cloudFoundryName == null) {
+			LoggerFactory.getLogger(Service.class).error("IAS Service is not yet supported!!!"); // TODO remove
+		}
 		return cloudFoundryName;
 	}
 }
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 a57b415b..4384bc51 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
@@ -37,4 +37,11 @@ public final class TokenClaims {
 
 		public static final String TRUSTED_CLIENT_ID_SUFFIX = "trustedclientidsuffix";
 	}
+
+	// SAP User token
+	/* public final class SAP_ID {
+		private SAP_ID() { }
+		// service_instance_id
+	}*/
+
 }
diff --git a/java-security/pom.xml b/java-security/pom.xml
index e1100269..44492f8f 100644
--- a/java-security/pom.xml
+++ b/java-security/pom.xml
@@ -177,6 +177,16 @@
 					</execution>
 				</executions>
 			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-surefire-plugin</artifactId>
+				<version>3.0.0-M4</version>
+				<configuration>
+					<environmentVariables>
+						<IAS_SERVICE_NAME>identity-beta</IAS_SERVICE_NAME>
+					</environmentVariables>
+				</configuration>
+			</plugin>
 		</plugins>
 	</build>
 
diff --git a/java-security/src/main/java/com/sap/cloud/security/adapter/spring/SAPOfflineTokenServicesCloud.java b/java-security/src/main/java/com/sap/cloud/security/adapter/spring/SAPOfflineTokenServicesCloud.java
index fc7b8a1e..f7b36809 100644
--- a/java-security/src/main/java/com/sap/cloud/security/adapter/spring/SAPOfflineTokenServicesCloud.java
+++ b/java-security/src/main/java/com/sap/cloud/security/adapter/spring/SAPOfflineTokenServicesCloud.java
@@ -3,12 +3,7 @@ package com.sap.cloud.security.adapter.spring;
 import com.sap.cloud.security.config.Environments;
 import com.sap.cloud.security.config.OAuth2ServiceConfiguration;
 import com.sap.cloud.security.config.cf.CFConstants;
-import com.sap.cloud.security.token.AccessToken;
-import com.sap.cloud.security.token.ScopeConverter;
-import com.sap.cloud.security.token.SecurityContext;
-import com.sap.cloud.security.token.Token;
-import com.sap.cloud.security.token.XsuaaScopeConverter;
-import com.sap.cloud.security.token.XsuaaToken;
+import com.sap.cloud.security.token.*;
 import com.sap.cloud.security.token.validation.ValidationResult;
 import com.sap.cloud.security.token.validation.Validator;
 import com.sap.cloud.security.token.validation.validators.JwtValidatorBuilder;
@@ -217,6 +212,8 @@ public class SAPOfflineTokenServicesCloud implements ResourceServerTokenServices
 			switch (serviceConfiguration.getService()) {
 			case XSUAA:
 				return new XsuaaToken(accessToken).withScopeConverter(xsuaaScopeConverter);
+			case IAS:
+				return new SapIdToken(accessToken);
 			default:
 				// TODO support IAS
 				throw new InvalidTokenException(
diff --git a/java-security/src/main/java/com/sap/cloud/security/config/cf/CFEnvironment.java b/java-security/src/main/java/com/sap/cloud/security/config/cf/CFEnvironment.java
index ca112929..5fb406e8 100644
--- a/java-security/src/main/java/com/sap/cloud/security/config/cf/CFEnvironment.java
+++ b/java-security/src/main/java/com/sap/cloud/security/config/cf/CFEnvironment.java
@@ -1,5 +1,6 @@
 package com.sap.cloud.security.config.cf;
 
+import static com.sap.cloud.security.config.Service.IAS;
 import static com.sap.cloud.security.config.Service.XSUAA;
 import static com.sap.cloud.security.config.cf.CFConstants.VCAP_APPLICATION;
 import static com.sap.cloud.security.config.cf.CFConstants.VCAP_SERVICES;
@@ -49,12 +50,9 @@ public class CFEnvironment implements Environment {
 	}
 
 	@Nullable
-	// @Override // TODO IAS
+	@Override
 	public OAuth2ServiceConfiguration getIasConfiguration() {
-		// TODO IAS
-		// return
-		// loadAll(IAS).stream().filter(Objects::nonNull).findFirst().orElse(null);
-		throw new UnsupportedOperationException("Bindings of IAS Identity Service is not yet supported.");
+		return loadAllForService(IAS).stream().filter(Objects::nonNull).findFirst().orElse(null);
 	}
 
 	@Override
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
new file mode 100644
index 00000000..2165976d
--- /dev/null
+++ b/java-security/src/main/java/com/sap/cloud/security/servlet/IasTokenAuthenticator.java
@@ -0,0 +1,32 @@
+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.Token;
+
+import javax.annotation.Nullable;
+
+public class IasTokenAuthenticator extends AbstractTokenAuthenticator {
+
+	@Override
+	public Token extractFromHeader(String authorizationHeader) {
+		return new SapIdToken(authorizationHeader);
+	}
+
+	@Override
+	protected OAuth2ServiceConfiguration getServiceConfiguration() {
+		OAuth2ServiceConfiguration config = serviceConfiguration != null ? serviceConfiguration
+				: Environments.getCurrent().getIasConfiguration();
+		if (config == null) {
+			throw new IllegalStateException("There must be a service configuration.");
+		}
+		return config;
+	}
+
+	@Nullable
+	@Override
+	protected OAuth2ServiceConfiguration getOtherServiceConfiguration() {
+		return null;
+	}
+}
diff --git a/samples/java-security-usage/pom.xml b/samples/java-security-usage/pom.xml
index e1f35c00..cd9c36aa 100755
--- a/samples/java-security-usage/pom.xml
+++ b/samples/java-security-usage/pom.xml
@@ -10,6 +10,7 @@
         <maven.compiler.source>1.8</maven.compiler.source>
         <maven.compiler.target>1.8</maven.compiler.target>
         <sap.cloud.security.version>2.6.2</sap.cloud.security.version>
+        <slf4j.api.version>1.7.30</slf4j.api.version>
         <apache.httpclient.version>4.5.8</apache.httpclient.version>
         <javax.servlet.api.version>3.1.0</javax.servlet.api.version>
         <wiremock.version>2.25.1</wiremock.version>
@@ -24,6 +25,13 @@
             <artifactId>httpclient</artifactId>
             <version>${apache.httpclient.version}</version>
         </dependency>
+        <!-- logging -->
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <version>${slf4j.api.version}</version>
+            <scope>compile</scope>
+        </dependency>
         <!-- security -->
         <dependency>
             <groupId>com.sap.cloud.security</groupId>


Test patch:
diff --git a/java-security-test/src/main/java/com/sap/cloud/security/test/ApplicationServerOptions.java b/java-security-test/src/main/java/com/sap/cloud/security/test/ApplicationServerOptions.java
index 0064e788..431a82da 100644
--- a/java-security-test/src/main/java/com/sap/cloud/security/test/ApplicationServerOptions.java
+++ b/java-security-test/src/main/java/com/sap/cloud/security/test/ApplicationServerOptions.java
@@ -4,6 +4,7 @@ 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.servlet.IasTokenAuthenticator;
 import com.sap.cloud.security.servlet.TokenAuthenticator;
 import com.sap.cloud.security.servlet.XsuaaTokenAuthenticator;
 import com.sap.cloud.security.xsuaa.Assertions;
@@ -46,7 +47,7 @@ public class ApplicationServerOptions {
 
 	/**
 	 * Creates an instance of ApplicationServerOptions.
-	 * 
+	 *
 	 * @param service
 	 *            the identity service
 	 * @return the application server options.
@@ -58,10 +59,13 @@ public class ApplicationServerOptions {
 		case XSUAA:
 			instance = forXsuaaService(SecurityTestRule.DEFAULT_APP_ID, SecurityTestRule.DEFAULT_CLIENT_ID);
 			break;
-		/*
-		 * SecurityTestRule case IAS: instance = new ApplicationServerOptions(new
-		 * IasTokenAuthenticator()); break;
-		 */
+		case IAS:
+			instance = new ApplicationServerOptions(new IasTokenAuthenticator()
+					.withServiceConfiguration(OAuth2ServiceConfigurationBuilder.forService(Service.IAS)
+							.withClientId(SecurityTestRule.DEFAULT_CLIENT_ID)
+							.withUrl("http://localhost")
+							.build()));
+			break;
 		default:
 			throw new UnsupportedOperationException("Identity Service " + service + " is not yet supported.");
 		}
diff --git a/java-security-test/src/test/java/com/sap/cloud/security/test/SecurityTestRuleTest.java b/java-security-test/src/test/java/com/sap/cloud/security/test/SecurityTestRuleTest.java
index 2966941f..47246b3f 100644
--- a/java-security-test/src/test/java/com/sap/cloud/security/test/SecurityTestRuleTest.java
+++ b/java-security-test/src/test/java/com/sap/cloud/security/test/SecurityTestRuleTest.java
@@ -1,6 +1,5 @@
 package com.sap.cloud.security.test;
 
-import com.sap.cloud.security.config.Service;
 import com.sap.cloud.security.json.DefaultJsonObject;
 import com.sap.cloud.security.json.JsonObject;
 import com.sap.cloud.security.token.Token;
@@ -29,6 +28,7 @@ import java.util.Base64;
 import java.util.List;
 import java.util.stream.Collectors;
 
+import static com.sap.cloud.security.config.Service.IAS;
 import static com.sap.cloud.security.config.Service.XSUAA;
 import static com.sap.cloud.security.test.ApplicationServerOptions.forService;
 import static org.assertj.core.api.Assertions.assertThat;
@@ -153,14 +153,15 @@ public class SecurityTestRuleTest {
 		}
 	}
 
-	// TODO IAS
-	public static class SecurityTestRuleApplicationServerFaults {
+	public static class SecurityTestRuleApplicationServer_IAS {
+
+		@Rule
+		public SecurityTestRule rule = SecurityTestRule.getInstance(IAS);
 
 		@Test
-		public void onlyXsuaaIsSupportedYet() {
-			assertThatThrownBy(() -> SecurityTestRule.getInstance(Service.IAS))
-					.isInstanceOf(UnsupportedOperationException.class)
-					.hasMessageContaining(String.format("Identity Service %s is not yet supported", Service.IAS));
+		public void testRuleIsInitializedCorrectly() {
+			assertThat(rule.getApplicationServerUri()).isNull();
+			assertThat(rule.getWireMockServer()).isNotNull();
 		}
 
 	}
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 15bb04a9..74e47080 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
@@ -1,5 +1,6 @@
 package com.sap.cloud.security.adapter.spring;
 
+import com.sap.cloud.security.adapter.spring.SAPOfflineTokenServicesCloud;
 import com.sap.cloud.security.config.OAuth2ServiceConfiguration;
 import com.sap.cloud.security.config.OAuth2ServiceConfigurationBuilder;
 import com.sap.cloud.security.config.Service;
@@ -31,10 +32,12 @@ public class SAPOfflineTokenServicesCloudTest {
 
 	private SAPOfflineTokenServicesCloud cut;
 	private String xsuaaToken;
+	private String iasToken;
 	private JwtValidatorBuilder jwtValidatorBuilderMock;
 
 	public SAPOfflineTokenServicesCloudTest() throws IOException {
 		xsuaaToken = IOUtils.resourceToString("/xsuaaCCAccessTokenRSA256.txt", StandardCharsets.UTF_8);
+		iasToken = IOUtils.resourceToString("/iasOidcTokenRSA256.txt", StandardCharsets.UTF_8);
 	}
 
 	@Before
@@ -71,6 +74,21 @@ public class SAPOfflineTokenServicesCloudTest {
 		assertThat(SecurityContext.getToken().getTokenValue()).isEqualTo(xsuaaToken);
 	}
 
+	@Test
+	public void loadAuthentication_ias() {
+		OAuth2ServiceConfiguration configuration = OAuth2ServiceConfigurationBuilder
+				.forService(Service.IAS)
+				.withProperty(CFConstants.CLIENT_ID, "clientId")
+				.build();
+
+		cut = new SAPOfflineTokenServicesCloud(configuration, jwtValidatorBuilderMock);
+
+		cut.afterPropertiesSet();
+		OAuth2Authentication authentication = cut.loadAuthentication(iasToken);
+
+		assertThat(authentication.isAuthenticated()).isTrue();
+	}
+
 	@Test
 	public void loadAuthenticationWithLocalScopes() throws IOException {
 		xsuaaToken = IOUtils.resourceToString("/xsuaaUserInfoAdapterToken.txt", StandardCharsets.UTF_8);
diff --git a/java-security/src/test/java/com/sap/cloud/security/config/cf/CFEnvironmentTest.java b/java-security/src/test/java/com/sap/cloud/security/config/cf/CFEnvironmentTest.java
index e2e52ad4..f12971ea 100644
--- a/java-security/src/test/java/com/sap/cloud/security/config/cf/CFEnvironmentTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/config/cf/CFEnvironmentTest.java
@@ -75,17 +75,14 @@ public class CFEnvironmentTest {
 				"Please check the service binding.");
 	}
 
-	// TODO IAS
-	@Test(expected = UnsupportedOperationException.class)
+	@Test
 	public void getConfigurationOfOneIasInstance() {
 		cut = CFEnvironment.getInstance((str) -> vcapIas, (str) -> null);
 		assertThat(cut.getIasConfiguration()).isSameAs(cut.getIasConfiguration());
 		assertThat(cut.getIasConfiguration().getService()).isEqualTo(Service.IAS);
-		assertThat(cut.getIasConfiguration().getClientId()).isEqualTo("T000297");
+		assertThat(cut.getIasConfiguration().getClientId()).isEqualTo("T000310");
 		assertThat(cut.getIasConfiguration().getClientSecret()).startsWith("pCghfbrL");
-		// assertThat(cut.getIasServiceConfiguration().getDomain()).isEqualTo("auth.com");
-		assertThat(cut.getIasConfiguration().getUrl().toString())
-				.isEqualTo("https://application.acc.ondemand.com");
+		assertThat(cut.getIasConfiguration().getUrl().toString()).isEqualTo("https://myauth.com");
 		assertThat(cut.getIasConfiguration().isLegacyMode()).isFalse();
 
 		assertThat(cut.getXsuaaConfiguration()).isNull();
@@ -106,7 +103,7 @@ public class CFEnvironmentTest {
 		assertThat(cut.getNumberOfXsuaaConfigurations()).isEqualTo(1);
 		assertThat(cut.getXsuaaConfigurationForTokenExchange()).isSameAs(cut.getXsuaaConfiguration());
 
-		// assertThat(cut.getIasConfiguration()).isNull(); // TODO IAS
+		assertThat(cut.getIasConfiguration()).isNull();
 	}
 
 	@Test
@@ -181,7 +178,7 @@ public class CFEnvironmentTest {
 		assertThat(cut.getXsuaaConfigurationForTokenExchange()).isNull();
 		assertThat(cut.loadForServicePlan(Service.IAS, Plan.DEFAULT)).isNull();
 		assertThat(CFEnvironment.getInstance().getXsuaaConfiguration()).isNull();
-		// assertThat(cut.getIasConfiguration()).isNull(); // TODO IAS
+		assertThat(cut.getIasConfiguration()).isNull();
 	}
 
 	@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
new file mode 100644
index 00000000..b96b7a56
--- /dev/null
+++ b/java-security/src/test/java/com/sap/cloud/security/servlet/IasTokenAuthenticatorTest.java
@@ -0,0 +1,151 @@
+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;
+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.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+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.times;
+import static org.mockito.Mockito.when;
+
+public class IasTokenAuthenticatorTest {
+
+	private final static HttpServletResponse HTTP_RESPONSE = Mockito.mock(HttpServletResponse.class);
+
+	private SapIdToken token;
+
+	private AbstractTokenAuthenticator cut;
+
+	public IasTokenAuthenticatorTest() throws IOException {
+		token = new SapIdToken(IOUtils.resourceToString("/iasOidcTokenRSA256.txt", UTF_8));
+	}
+
+	@Before
+	public void setUp() throws IOException {
+		OAuth2ServiceConfiguration configuration = OAuth2ServiceConfigurationBuilder
+				.forService(Service.IAS)
+				.withProperty(CFConstants.URL, "https://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);
+
+		cut = new IasTokenAuthenticator()
+				.withServiceConfiguration(configuration)
+				.withHttpClient(httpClientMock);
+	}
+
+	@Test
+	public void validateWhenConfigurationIsNull() {
+		cut = new IasTokenAuthenticator();
+
+		HttpServletRequest httpRequest = createRequestWithToken(token.getTokenValue());
+
+		TokenAuthenticationResult response = cut.validateRequest(httpRequest, HTTP_RESPONSE);
+		assertThat(response.isAuthenticated()).isFalse();
+		assertThat(response.getUnauthenticatedReason())
+				.contains("Unexpected error occurred: There must be a service configuration.");
+	}
+
+	@Test
+	public void validateRequest_noHeader_isUnauthenticated() {
+		HttpServletRequest httpRequest = createRequestWithoutToken();
+
+		TokenAuthenticationResult response = cut.validateRequest(httpRequest, HTTP_RESPONSE);
+
+		assertThat(response.isAuthenticated()).isFalse();
+		assertThat(response.getUnauthenticatedReason()).contains("Authorization header is missing");
+	}
+
+	@Test
+	public void validateRequest_invalidToken_isUnauthenticated() {
+		String errorMessage = "JWT token does not consist of 'header'.'payload'.'signature'";
+		HttpServletRequest httpRequest = createRequestWithToken("Bearer invalid");
+
+		TokenAuthenticationResult response = cut.validateRequest(httpRequest, HTTP_RESPONSE);
+
+		assertThat(response.isAuthenticated()).isFalse();
+		assertThat(response.getUnauthenticatedReason()).contains(errorMessage);
+	}
+
+	@Test
+	public void validateRequest_validToken_containedInSecurityContext() {
+		HttpServletRequest httpRequest = createRequestWithToken(token.getTokenValue());
+
+		TokenAuthenticationResult response = cut.validateRequest(httpRequest, HTTP_RESPONSE);
+
+		assertThat(response.getUnauthenticatedReason()).isEmpty();
+		assertThat(response.isAuthenticated()).isTrue();
+		assertThat(response.getToken()).isSameAs(SecurityContext.getToken());
+	}
+
+	@Test
+	public void validateRequest_validToken_listenerIsCalled() {
+		HttpServletRequest httpRequest = createRequestWithToken(token.getTokenValue());
+		ValidationListener validationListener1 = Mockito.mock(ValidationListener.class);
+		ValidationListener validationListener2 = Mockito.mock(ValidationListener.class);
+
+		cut.withValidationListener(validationListener1)
+				.withValidationListener(validationListener2)
+				.validateRequest(httpRequest, HTTP_RESPONSE);
+
+		Mockito.verify(validationListener1, times(1)).onValidationSuccess();
+		Mockito.verify(validationListener2, times(1)).onValidationSuccess();
+		Mockito.verifyNoMoreInteractions(validationListener1);
+		Mockito.verifyNoMoreInteractions(validationListener2);
+	}
+
+	@Test
+	public void validateRequest_invalidToken_listenerIsCalled() {
+		HttpServletRequest httpRequest = createRequestWithToken(token.getTokenValue() + "B");
+		ValidationListener validationListener1 = Mockito.mock(ValidationListener.class);
+		ValidationListener validationListener2 = Mockito.mock(ValidationListener.class);
+
+		cut.withValidationListener(validationListener1)
+				.withValidationListener(validationListener2)
+				.validateRequest(httpRequest, HTTP_RESPONSE);
+
+		Mockito.verify(validationListener1, times(1)).onValidationError(any());
+		Mockito.verify(validationListener2, times(1)).onValidationError(any());
+		Mockito.verifyNoMoreInteractions(validationListener2);
+		Mockito.verifyNoMoreInteractions(validationListener2);
+	}
+
+	private HttpServletRequest createRequestWithoutToken() {
+		return Mockito.mock(HttpServletRequest.class);
+	}
+
+	private HttpServletRequest createRequestWithToken(String bearerAuthorizationHeader) {
+		HttpServletRequest httpRequest = createRequestWithoutToken();
+		when(httpRequest.getHeader(HttpHeaders.AUTHORIZATION)).thenReturn("Bearer " + bearerAuthorizationHeader);
+		return httpRequest;
+	}
+
+}
diff --git a/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/JwtValidatorBuilderTest.java b/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/JwtValidatorBuilderTest.java
index 398c880b..d63bd67e 100644
--- a/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/JwtValidatorBuilderTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/JwtValidatorBuilderTest.java
@@ -2,7 +2,6 @@ package com.sap.cloud.security.token.validation.validators;
 
 import com.sap.cloud.security.config.OAuth2ServiceConfiguration;
 import com.sap.cloud.security.config.OAuth2ServiceConfigurationBuilder;
-import com.sap.cloud.security.config.Service;
 import com.sap.cloud.security.config.cf.CFConstants;
 import com.sap.cloud.security.token.Token;
 import com.sap.cloud.security.token.validation.*;
@@ -14,8 +13,8 @@ import org.mockito.junit.MockitoJUnitRunner;
 
 import java.util.List;
 
-import static com.sap.cloud.security.config.Service.XSUAA;
 import static com.sap.cloud.security.config.Service.IAS;
+import static com.sap.cloud.security.config.Service.XSUAA;
 import static org.assertj.core.api.Assertions.assertThat;
 
 @RunWith(MockitoJUnitRunner.class)