Repository: SAP/cloud-security-xsuaa-integration
Pull request #211 (2020-01-17 15:40:04)

Expiration missing should produce invalid tokens
This PR implements the business requirement that JWT tokens should *not* be valid if they do not contain an expiration date (exp).
Until now expiration was optional.

I have created another "NO_EXPIRE_DATE" inside the test package because I did not want to move the existing NO_EXPIRE_DATE constant from the java-security package into the production code. 
I have also created an `withExpiration` method to the `JwtGenerator` to simplify setting custom expiration dates.



Patch:
diff --git a/java-security/src/main/java/com/sap/cloud/security/servlet/AbstractTokenAuthenticator.java b/java-security/src/main/java/com/sap/cloud/security/servlet/AbstractTokenAuthenticator.java
index 366fd135..c4752bcc 100644
--- a/java-security/src/main/java/com/sap/cloud/security/servlet/AbstractTokenAuthenticator.java
+++ b/java-security/src/main/java/com/sap/cloud/security/servlet/AbstractTokenAuthenticator.java
@@ -75,7 +75,6 @@ public abstract class AbstractTokenAuthenticator implements TokenAuthenticator {
 	 * configured.
 	 * 
 	 * @return the actual service configuration
-	 * @throws IllegalStateException in case service configuration is null
 	 */
 	protected abstract OAuth2ServiceConfiguration getServiceConfiguration();
 
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 9767b582..967c72a9 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
@@ -24,12 +24,7 @@ public class XsuaaTokenAuthenticator extends AbstractTokenAuthenticator {
 
 	@Override
 	protected OAuth2ServiceConfiguration getServiceConfiguration() {
-		OAuth2ServiceConfiguration config =  serviceConfiguration != null ? serviceConfiguration :
-				Environments.getCurrent().getXsuaaConfiguration();
-		if(config == null) {
-			throw new IllegalStateException("There must be a service configuration.");
-		}
-		return config;
+		return serviceConfiguration != null ? serviceConfiguration : Environments.getCurrent().getXsuaaConfiguration();
 	}
 
 	@Override
diff --git a/java-security/src/main/java/com/sap/cloud/security/spring/adapter/SAPOfflineTokenServicesCloud.java b/java-security/src/main/java/com/sap/cloud/security/spring/adapter/SAPOfflineTokenServicesCloud.java
index 9a09e510..794c3623 100644
--- a/java-security/src/main/java/com/sap/cloud/security/spring/adapter/SAPOfflineTokenServicesCloud.java
+++ b/java-security/src/main/java/com/sap/cloud/security/spring/adapter/SAPOfflineTokenServicesCloud.java
@@ -4,7 +4,6 @@ import com.sap.cloud.security.config.OAuth2ServiceConfiguration;
 import com.sap.cloud.security.token.Token;
 import com.sap.cloud.security.token.XsuaaToken;
 import com.sap.cloud.security.token.validation.CombiningValidator;
-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;
 import org.springframework.beans.factory.InitializingBean;
@@ -15,7 +14,6 @@ import org.springframework.security.oauth2.provider.AuthorizationRequest;
 import org.springframework.security.oauth2.provider.OAuth2Authentication;
 import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
 
-import javax.annotation.Nonnull;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Set;
@@ -38,28 +36,16 @@ public class SAPOfflineTokenServicesCloud implements ResourceServerTokenServices
 	}
 
 	@Override
-	public OAuth2Authentication loadAuthentication(@Nonnull String accessToken)
+	public OAuth2Authentication loadAuthentication(String accessToken)
 			throws AuthenticationException, InvalidTokenException {
-		XsuaaToken token = checkAndCreateToken(accessToken);
+		XsuaaToken token = new XsuaaToken(accessToken);
 		Set<String> scopes = token.getScopes().stream().collect(Collectors.toSet());
-		ValidationResult validationResult = tokenValidator.validate(token);
 
-		if (validationResult.isValid()) {
-			AuthorizationRequest authorizationRequest = new AuthorizationRequest(new HashMap<>(), null,
-					serviceConfiguration.getClientId(), scopes, new HashSet<>(), null,
-					true, "", "", null);
-			return new OAuth2Authentication(authorizationRequest.createOAuth2Request(), null);
-		} else {
-			throw new InvalidTokenException(validationResult.getErrorDescription());
-		}
-	}
+		AuthorizationRequest authorizationRequest = new AuthorizationRequest(new HashMap<>(), null,
+				serviceConfiguration.getClientId(), scopes, new HashSet<>(), null,
+				tokenValidator.validate(token).isValid(), "", "", null);
 
-	private XsuaaToken checkAndCreateToken(@Nonnull String accessToken) {
-		try {
-			return new XsuaaToken(accessToken);
-		} catch (Exception e) {
-			throw new InvalidTokenException(e.getMessage());
-		}
+		return new OAuth2Authentication(authorizationRequest.createOAuth2Request(), null);
 	}
 
 	@Override
diff --git a/java-security/src/main/java/com/sap/cloud/security/token/XsuaaScopeConverter.java b/java-security/src/main/java/com/sap/cloud/security/token/XsuaaScopeConverter.java
index bdfc91c7..fb341534 100644
--- a/java-security/src/main/java/com/sap/cloud/security/token/XsuaaScopeConverter.java
+++ b/java-security/src/main/java/com/sap/cloud/security/token/XsuaaScopeConverter.java
@@ -1,7 +1,5 @@
 package com.sap.cloud.security.token;
 
-import com.sap.cloud.security.xsuaa.Assertions;
-
 import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -21,7 +19,6 @@ public class XsuaaScopeConverter implements ScopeConverter {
 	 *            the xs application name e.g. myapp!t123.
 	 */
 	public XsuaaScopeConverter(String appId) {
-		Assertions.assertHasText(appId, "appId must not be null or empty.");
 		this.globalScopePattern = Pattern.compile(appId + "\\.(.+)");
 	}
 
diff --git a/java-security/src/main/java/com/sap/cloud/security/token/XsuaaToken.java b/java-security/src/main/java/com/sap/cloud/security/token/XsuaaToken.java
index 2abe5b47..60e3af4b 100644
--- a/java-security/src/main/java/com/sap/cloud/security/token/XsuaaToken.java
+++ b/java-security/src/main/java/com/sap/cloud/security/token/XsuaaToken.java
@@ -129,7 +129,7 @@ public class XsuaaToken extends AbstractToken {
 	 * @return true if local scope is available
 	 **/
 	public boolean hasLocalScope(@Nonnull String scope) {
-		Assertions.assertNotNull(scopeConverter, "hasLocalScope method requires scopeConverter, which must not be null");
+		Assertions.assertNotNull(scopeConverter, "scopeConverter must not be null");
 		return scopeConverter.convert(getScopes()).contains(scope);
 	}
 
diff --git a/java-security/src/main/java/com/sap/cloud/security/token/validation/CombiningValidator.java b/java-security/src/main/java/com/sap/cloud/security/token/validation/CombiningValidator.java
index b45849e5..33016c0e 100644
--- a/java-security/src/main/java/com/sap/cloud/security/token/validation/CombiningValidator.java
+++ b/java-security/src/main/java/com/sap/cloud/security/token/validation/CombiningValidator.java
@@ -1,7 +1,5 @@
 package com.sap.cloud.security.token.validation;
 
-import com.sap.cloud.security.xsuaa.Assertions;
-
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -19,7 +17,6 @@ public class CombiningValidator<T> implements Validator<T> {
 	private final Set<ValidationListener> validationListeners = new HashSet<>();
 
 	public CombiningValidator(List<Validator<T>> validators) {
-		Assertions.assertNotNull(validators, "validators must not be null.");
 		this.validators = validators;
 	}
 
diff --git a/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtTimestampValidator.java b/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtTimestampValidator.java
index f42a027e..1c98dba5 100644
--- a/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtTimestampValidator.java
+++ b/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtTimestampValidator.java
@@ -5,6 +5,7 @@ import static com.sap.cloud.security.token.validation.ValidationResults.createIn
 import javax.annotation.Nullable;
 
 import com.sap.cloud.security.token.Token;
+import com.sap.cloud.security.token.validation.ValidationListener;
 import com.sap.cloud.security.token.validation.ValidationResult;
 import com.sap.cloud.security.token.validation.ValidationResults;
 import com.sap.cloud.security.token.validation.Validator;
@@ -52,6 +53,8 @@ public class JwtTimestampValidator implements Validator<Token> {
 		Instant expiration = token.getExpiration();
 		if (expiration != null) {
 			validationResult = checkExpiration(expiration);
+		} else {
+			return ValidationResults.createInvalid("Jwt does not contain expiration (exp) claim. Cannot be validated!");
 		}
 
 		Instant notBefore = token.getNotBefore();
diff --git a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServicePropertySourceFactory.java b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServicePropertySourceFactory.java
index 1baf4858..d93ffb1f 100644
--- a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServicePropertySourceFactory.java
+++ b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServicePropertySourceFactory.java
@@ -60,7 +60,6 @@ public class XsuaaServicePropertySourceFactory implements PropertySourceFactory
 		} else {
 			vcapServicesParser = new XsuaaServicesParser();
 		}
-		logger.info("Parse {} XSUAA properties.");
 		properties = vcapServicesParser.parseCredentials();
 		logger.info("Parsed {} XSUAA properties.", properties.size());
 		return create(XSUAA_PROPERTIES_KEY, properties);


Test patch:
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 197c779b..5e35d3f5 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
@@ -14,7 +14,9 @@ import org.json.JSONObject;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.annotation.Nonnull;
 import java.security.*;
+import java.time.Instant;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -22,6 +24,8 @@ import java.util.stream.Collectors;
  * Jwt {@link Token} builder class to generate tokes for testing purposes.
  */
 public class JwtGenerator {
+	public static final Instant NO_EXPIRE_DATE = new GregorianCalendar(2190, 11, 31).getTime().toInstant();
+
 	private static final Logger logger = LoggerFactory.getLogger(JwtGenerator.class);
 
 	private static final char DOT = '.';
@@ -94,6 +98,18 @@ public class JwtGenerator {
 		return this;
 	}
 
+	/**
+	 * Sets the expiration claim (exp) of the token to the given moment in time.
+	 *
+	 * @param expiration
+	 *            the moment in time when the token will be expired.
+	 * @return the builder object.
+	 */
+	public JwtGenerator withExpiration(@Nonnull Instant expiration) {
+		jsonPayload.put(TokenClaims.EXPIRATION, expiration.getEpochSecond());
+		return this;
+	}
+
 	/**
 	 * Sets the signature algorithm that is used to create the signature of the
 	 * token.
diff --git a/java-security-test/src/main/java/com/sap/cloud/security/test/SecurityTestRule.java b/java-security-test/src/main/java/com/sap/cloud/security/test/SecurityTestRule.java
index 146547b1..7696174c 100644
--- a/java-security-test/src/main/java/com/sap/cloud/security/test/SecurityTestRule.java
+++ b/java-security-test/src/main/java/com/sap/cloud/security/test/SecurityTestRule.java
@@ -161,9 +161,9 @@ public class SecurityTestRule extends ExternalResource {
 	}
 
 	/**
-	 * Overwrites the private/public key pair to be used. The private key is used to
-	 * sign the jwt token. The public key is provided by jwks endpoint (on behalf of
-	 * WireMock). Checked exceptions are caught and rethrown as runtime
+	 * Same as {@link SecurityTestRule#setKeys(RSAKeys)} except you can pass in the
+	 * path to the public and private keys and the {@link RSAKeys} object will be
+	 * built for you. Any checked exceptions are caught and rethrown as runtime
 	 * exceptions for test convenience.
 	 *
 	 * @param publicKeyPath
@@ -181,6 +181,22 @@ public class SecurityTestRule extends ExternalResource {
 		return this;
 	}
 
+	/**
+	 * Overwrites the private/public key pair to be used. The private key is used to
+	 * sign the jwt token. The public key is provided by jwks endpoint (on behalf of
+	 * WireMock).
+	 * <p>
+	 * It needs to be configured before the {@link #before()} method.
+	 *
+	 * @param keys
+	 *            the private/public key pair.
+	 * @return the rule itself.
+	 */
+	public SecurityTestRule setKeys(RSAKeys keys) {
+		this.keys = keys;
+		return this;
+	}
+
 	@Override
 	protected void before() throws Exception {
 		if (useApplicationServer) {
@@ -199,6 +215,7 @@ public class SecurityTestRule extends ExternalResource {
 	 */
 	public JwtGenerator getPreconfiguredJwtGenerator() {
 		JwtGenerator jwtGenerator = JwtGenerator.getInstance(service)
+				.withExpiration(JwtGenerator.NO_EXPIRE_DATE)
 				.withClaimValue(TokenClaims.XSUAA.CLIENT_ID, clientId)
 				.withPrivateKey(keys.getPrivate());
 		switch (service) {
diff --git a/java-security-test/src/test/java/com/sap/cloud/security/test/JwtGeneratorTest.java b/java-security-test/src/test/java/com/sap/cloud/security/test/JwtGeneratorTest.java
index fb4cb5a0..c17cc120 100644
--- a/java-security-test/src/test/java/com/sap/cloud/security/test/JwtGeneratorTest.java
+++ b/java-security-test/src/test/java/com/sap/cloud/security/test/JwtGeneratorTest.java
@@ -25,6 +25,9 @@ import java.nio.charset.StandardCharsets;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
 import java.security.SignatureException;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.ZoneOffset;
 import java.util.Properties;
 
 import static com.sap.cloud.security.config.Service.IAS;
@@ -135,6 +138,15 @@ public class JwtGeneratorTest {
 		assertThat(token.getClaimAsStringList(TokenClaims.AUDIENCE)).containsExactly(audiences);
 	}
 
+	@Test
+	public void withExpiration_createsTokenWithExpiration() {
+		Instant expiration = LocalDate.of(2019, 1, 1).atStartOfDay().toInstant(ZoneOffset.UTC);
+
+		Token token = cut.withExpiration(expiration).createToken();
+
+		assertThat(token.getExpiration()).isEqualTo(expiration);
+	}
+
 	@Test
 	public void deriveAudience_createsTokenWithDerivedAudiences() {
 		String[] scopes = { "openid", "app1.scope", "app2.sub.scope", "app2.scope", ".scopeWithoutAppId" };
@@ -205,6 +217,7 @@ public class JwtGeneratorTest {
 		Token token = cut
 				.withHeaderParameter(TokenHeader.JWKS_URL, "http://auth.com/token_keys")
 				.withClaimValue(TokenClaims.XSUAA.CLIENT_ID, "xs2.usertoken")
+				.withExpiration(JwtGenerator.NO_EXPIRE_DATE)
 				.createToken();
 
 		ValidationResult result = tokenValidator.validate(token);
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 0a63c23f..1c837efa 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
@@ -40,13 +40,13 @@ public class SecurityTestRuleTest {
 	private static final int PORT = 8484;
 	private static final int APPLICATION_SERVER_PORT = 8383;
 	private static final String UTF_8 = StandardCharsets.UTF_8.displayName();
-	private static final String PUBLIC_KEY_PATH = "src/main/resources/publicKey.txt";
-	private static final String PRIVATE_KEY_PATH = "src/main/resources/privateKey.txt";
+
+	private static final RSAKeys RSA_KEYS = RSAKeys.generate();
 
 	@ClassRule
 	public static SecurityTestRule cut = SecurityTestRule.getInstance(XSUAA)
 			.setPort(PORT)
-			.setKeys(PUBLIC_KEY_PATH, PRIVATE_KEY_PATH)
+			.setKeys(RSA_KEYS)
 			.useApplicationServer(forService(XSUAA).usePort(APPLICATION_SERVER_PORT))
 			.addApplicationServlet(TestServlet.class, "/hi");
 
@@ -54,15 +54,7 @@ public class SecurityTestRuleTest {
 	public void getTokenKeysRequest_responseContainsExpectedTokenKeys()
 			throws IOException, InvalidKeySpecException, NoSuchAlgorithmException {
 
-		HttpGet httpGet = new HttpGet("http://localhost:" + PORT + "/token_keys");
-		try (CloseableHttpResponse response = HttpClients.createDefault().execute(httpGet)) {
-			assertThat(response.getStatusLine().getStatusCode()).isEqualTo(HttpStatus.SC_OK);
-			JsonWebKeySet keySet = JsonWebKeySetFactory.createFromJson(readContent(response));
-			PublicKey actualPublicKey = keySet
-					.getKeyByAlgorithmAndId(JwtSignatureAlgorithm.RS256, "default-kid").getPublicKey();
-
-			assertThat(actualPublicKey).isEqualTo(RSAKeys.loadPublicKey(PUBLIC_KEY_PATH));
-		}
+		assertThatMockKeyServiceServesPublicKey(RSA_KEYS.getPublic(), PORT);
 	}
 
 	@Test
@@ -87,6 +79,13 @@ public class SecurityTestRuleTest {
 				.isEqualTo(cut.getPreconfiguredJwtGenerator().createToken().getAccessToken());
 	}
 
+	@Test
+	public void getPreconfiguredJwtGenerator_tokenHasExpirationDate() {
+		Token token = cut.getPreconfiguredJwtGenerator().createToken();
+
+		assertThat(token.hasClaim(TokenClaims.EXPIRATION)).isTrue();
+	}
+
 	@Test
 	public void servletFilterServesTestServlet() throws IOException {
 		HttpGet httpGet = new HttpGet(cut.getApplicationServerUri() + "/hi");
@@ -147,6 +146,22 @@ public class SecurityTestRuleTest {
 
 	}
 
+	public static class SecurityTestRuleWithRSAKeysPath {
+
+		private static final String PUBLIC_KEY_PATH = "src/main/resources/publicKey.txt";
+
+		@Rule
+		public SecurityTestRule rule = SecurityTestRule.getInstance(XSUAA)
+				.setKeys(PUBLIC_KEY_PATH, "src/main/resources/privateKey.txt");
+
+		@Test
+		public void testRuleIsInitializedCorrectly()
+				throws NoSuchAlgorithmException, IOException, InvalidKeySpecException {
+			assertThatMockKeyServiceServesPublicKey(RSAKeys.loadPublicKey(PUBLIC_KEY_PATH),
+					rule.getWireMockRule().port());
+		}
+	}
+
 	public static class TestServlet extends HttpServlet {
 		@Override
 		protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
@@ -162,4 +177,16 @@ public class SecurityTestRuleTest {
 				.collect(Collectors.joining());
 	}
 
+	private static void assertThatMockKeyServiceServesPublicKey(PublicKey expectedPublicKey, int mockKeyServicePort)
+			throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
+		HttpGet httpGet = new HttpGet("http://localhost:" + mockKeyServicePort + "/token_keys");
+		try (CloseableHttpResponse response = HttpClients.createDefault().execute(httpGet)) {
+			assertThat(response.getStatusLine().getStatusCode()).isEqualTo(HttpStatus.SC_OK);
+			JsonWebKeySet keySet = JsonWebKeySetFactory.createFromJson(readContent(response));
+			PublicKey actualPublicKey = keySet
+					.getKeyByAlgorithmAndId(JwtSignatureAlgorithm.RS256, "default-kid").getPublicKey();
+
+			assertThat(actualPublicKey).isEqualTo(expectedPublicKey);
+		}
+	}
 }
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 72806db8..5269f4d4 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
@@ -145,9 +145,6 @@ public class CFEnvironmentTest {
 		cut = CFEnvironment.getInstance((str) -> null, (str) -> null);
 
 		assertThat(cut.getXsuaaConfiguration()).isNull();
-		assertThat(cut.getNumberOfXsuaaConfigurations()).isEqualTo(0);
-		assertThat(cut.getXsuaaConfigurationForTokenExchange()).isNull();
-		assertThat(cut.loadForServicePlan(Service.IAS, Plan.DEFAULT)).isNull();
 		assertThat(CFEnvironment.getInstance().getXsuaaConfiguration()).isNull();
 		// assertThat(cut.getIasConfiguration()).isNull(); // TODO IAS
 	}
diff --git a/java-security/src/test/java/com/sap/cloud/security/servlet/XsuaaTokenAuthenticatorTest.java b/java-security/src/test/java/com/sap/cloud/security/servlet/XsuaaTokenAuthenticatorTest.java
index 467cfc92..448384cd 100644
--- a/java-security/src/test/java/com/sap/cloud/security/servlet/XsuaaTokenAuthenticatorTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/servlet/XsuaaTokenAuthenticatorTest.java
@@ -54,17 +54,6 @@ public class XsuaaTokenAuthenticatorTest {
 				.withServiceConfiguration(oAuth2ServiceConfiguration);
 	}
 
-	@Test
-	public void validateWhenConfigurationIsNull() {
-		cut = new XsuaaTokenAuthenticator();
-
-		HttpServletRequest httpRequest = createRequestWithToken(xsuaaToken.getBearerAccessToken());
-
-		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();
diff --git a/java-security/src/test/java/com/sap/cloud/security/spring/adapter/SAPOfflineTokenServicesCloudTest.java b/java-security/src/test/java/com/sap/cloud/security/spring/adapter/SAPOfflineTokenServicesCloudTest.java
index bbb03d6b..4d73429c 100644
--- a/java-security/src/test/java/com/sap/cloud/security/spring/adapter/SAPOfflineTokenServicesCloudTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/spring/adapter/SAPOfflineTokenServicesCloudTest.java
@@ -11,7 +11,6 @@ import org.apache.commons.io.IOUtils;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mockito;
-import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
 import org.springframework.security.oauth2.provider.OAuth2Authentication;
 
 import java.io.IOException;
@@ -36,7 +35,6 @@ public class SAPOfflineTokenServicesCloudTest {
 		cut = createSAPOfflineTokenServicesCloud((token) -> ValidationResults.createValid());
 	}
 
-
 	@Test
 	public void loadAuthentication() throws IOException {
 		cut.afterPropertiesSet();
@@ -47,30 +45,19 @@ public class SAPOfflineTokenServicesCloudTest {
 		assertThat(authentication.getOAuth2Request().getScope()).contains("openid");
 	}
 
-	@Test
-	public void loadAuthentication_tokenIsNull_throwsException() {
-		assertThatThrownBy(() -> cut.loadAuthentication(null)).isInstanceOf(InvalidTokenException.class);
-	}
-
-	@Test
-	public void loadAuthentication_tokenIsMalformed_throwsException() {
-		assertThatThrownBy(() -> cut.loadAuthentication("a.b.c")).isInstanceOf(InvalidTokenException.class);
-	}
-
-
 	@Test
 	public void readAccessToken() {
 		assertThatThrownBy(() -> cut.readAccessToken("token")).isInstanceOf(UnsupportedOperationException.class);
 	}
 
 	@Test
-	public void loadAuthentication_tokenValidationFailed_throwsException() {
-		String errorDescription = "just not valid";
-		cut = createSAPOfflineTokenServicesCloud((token) -> ValidationResults.createInvalid(errorDescription));
+	public void loadAuthentication_notAuthenticated() {
+		cut = createSAPOfflineTokenServicesCloud((token) -> ValidationResults.createInvalid("not valid"));
 		cut.afterPropertiesSet();
 
-		assertThatThrownBy(() -> cut.loadAuthentication(xsuaaToken)).isInstanceOf(InvalidTokenException.class).hasMessageContaining(errorDescription);
+		OAuth2Authentication authentication = cut.loadAuthentication(xsuaaToken);
 
+		assertThat(authentication.isAuthenticated()).isFalse();
 	}
 
 	@Test
@@ -84,12 +71,13 @@ public class SAPOfflineTokenServicesCloudTest {
 	}
 
 	@Test
-	public void createInstancWithEmptyConfiguration_throwsException() {
+	public void createInstanceWithPublicConstructor_notAuthenticated() {
 		cut =  new SAPOfflineTokenServicesCloud(Mockito.mock(OAuth2ServiceConfiguration.class));
 		cut.afterPropertiesSet();
-		assertThatThrownBy(() -> cut.loadAuthentication(xsuaaToken)).isInstanceOf(InvalidTokenException.class);
-	}
+		OAuth2Authentication authentication = cut.loadAuthentication(xsuaaToken);
 
+		assertThat(authentication.isAuthenticated()).isFalse();
+	}
 
 	private static class TestTokenValidatorSupplier implements Supplier<Validator<Token>> {
 		@Override public Validator<Token> get() {
diff --git a/java-security/src/test/java/com/sap/cloud/security/token/AbstractTokenTest.java b/java-security/src/test/java/com/sap/cloud/security/token/AbstractTokenTest.java
index 541d5af2..cb021091 100644
--- a/java-security/src/test/java/com/sap/cloud/security/token/AbstractTokenTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/token/AbstractTokenTest.java
@@ -1,6 +1,5 @@
 package com.sap.cloud.security.token;
 
-import com.sap.cloud.security.xsuaa.jwt.Base64JwtDecoder;
 import org.apache.commons.io.IOUtils;
 import org.junit.Test;
 
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 dd51a7b6..ff58415f 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
@@ -9,7 +9,6 @@ import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 public class SecurityContextTest {
 
@@ -22,21 +21,14 @@ public class SecurityContextTest {
 	}
 
 	@Test
-	public void setTokenAndRetrieve_NullToken() {
-		SecurityContext.setToken(TOKEN);
-		SecurityContext.setToken(null);
-		assertThat(SecurityContext.getToken()).isNull();
-	}
-
-	@Test
-	public void getInitialToken_isNull() {
+	public void securityContext_initialTokenIsNull() {
 		Token token = SecurityContext.getToken();
 
 		assertThat(token).isNull();
 	}
 
 	@Test
-	public void setTokenAndGet() {
+	public void initTokenAndRetrieve() {
 		SecurityContext.setToken(TOKEN);
 		Token token = SecurityContext.getToken();
 
diff --git a/java-security/src/test/java/com/sap/cloud/security/token/XsuaaScopeConverterTest.java b/java-security/src/test/java/com/sap/cloud/security/token/XsuaaScopeConverterTest.java
index d83a52a6..5dd9be43 100644
--- a/java-security/src/test/java/com/sap/cloud/security/token/XsuaaScopeConverterTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/token/XsuaaScopeConverterTest.java
@@ -6,7 +6,6 @@ import org.junit.Test;
 import java.util.List;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.assertj.core.util.Lists.newArrayList;
 
 public class XsuaaScopeConverterTest {
@@ -18,17 +17,6 @@ public class XsuaaScopeConverterTest {
 		cut = new XsuaaScopeConverter("myAppId!t1785");
 	}
 
-	@Test
-	public void constructsWithInvalidAppId_raisesIllegalArgumentException() {
-		assertThatThrownBy(() -> {
-			new XsuaaScopeConverter(null);
-		}).isInstanceOf(IllegalArgumentException.class).hasMessageContaining("appId must not be null or empty");
-
-		assertThatThrownBy(() -> {
-			new XsuaaScopeConverter("");
-		}).isInstanceOf(IllegalArgumentException.class).hasMessageContaining("appId must not be null or empty");
-	}
-
 	@Test
 	public void oneLocalScope() {
 		List<String> scope = newArrayList("myAppId!t1785.Read");
diff --git a/java-security/src/test/java/com/sap/cloud/security/token/XsuaaTokenTest.java b/java-security/src/test/java/com/sap/cloud/security/token/XsuaaTokenTest.java
index 9b22cb7d..f1002900 100644
--- a/java-security/src/test/java/com/sap/cloud/security/token/XsuaaTokenTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/token/XsuaaTokenTest.java
@@ -27,18 +27,6 @@ public class XsuaaTokenTest {
 		scopesToken = new XsuaaToken(IOUtils.resourceToString("/xsuaaScopesTokenRSA256.txt", UTF_8));
 	}
 
-
-	@Test
-	public void constructor_raiseIllegalArgumentExceptions() {
-		assertThatThrownBy(() -> {
-			new XsuaaToken("");
-		}).isInstanceOf(IllegalArgumentException.class).hasMessageContaining("accessToken must not be null / empty");
-
-		assertThatThrownBy(() -> {
-			new XsuaaToken("abc");
-		}).isInstanceOf(IllegalArgumentException.class).hasMessageContaining("JWT token does not consist of 'header'.'payload'.'signature'.");
-	}
-
 	@Test
 	public void getScopes() {
 		assertThat(clientCredentialsToken.getScopes()).containsExactly("ROLE_SERVICEBROKER", "uaa.resource");
@@ -99,22 +87,18 @@ public class XsuaaTokenTest {
 	public void getUniquePrincipalName() {
 		assertThat(XsuaaToken.getUniquePrincipalName("origin", "user"))
 				.isEqualTo("user/origin/user");
-	}
 
-	@Test
-	public void getUniquePrincipalName_raiseIllegalArgumentExceptions() {
 		assertThatThrownBy(() -> {
 			XsuaaToken.getUniquePrincipalName("origin/", "user");
-		}).isInstanceOf(IllegalArgumentException.class).hasMessageContaining("/");
+		}).isInstanceOf(IllegalArgumentException.class).hasMessageContainingAll("/");
 
 		assertThatThrownBy(() -> {
 			XsuaaToken.getUniquePrincipalName("origin", "");
-		}).isInstanceOf(IllegalArgumentException.class).hasMessageContaining("User");
+		}).isInstanceOf(IllegalArgumentException.class).hasMessageContainingAll("User");
 
 		assertThatThrownBy(() -> {
 			XsuaaToken.getUniquePrincipalName("", "user");
-		}).isInstanceOf(IllegalArgumentException.class).hasMessageContaining("Origin");
+		}).isInstanceOf(IllegalArgumentException.class).hasMessageContainingAll("Origin");
 	}
 
-
 }
\ No newline at end of file
diff --git a/java-security/src/test/java/com/sap/cloud/security/token/validation/CombiningValidatorTest.java b/java-security/src/test/java/com/sap/cloud/security/token/validation/CombiningValidatorTest.java
index f16d21a8..2074ff84 100644
--- a/java-security/src/test/java/com/sap/cloud/security/token/validation/CombiningValidatorTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/token/validation/CombiningValidatorTest.java
@@ -1,7 +1,6 @@
 package com.sap.cloud.security.token.validation;
 
 import com.sap.cloud.security.token.Token;
-import com.sap.cloud.security.token.XsuaaToken;
 import com.sap.cloud.security.token.validation.*;
 import org.junit.Test;
 import org.mockito.Mockito;
@@ -9,7 +8,6 @@ import org.mockito.Mockito;
 import java.util.ArrayList;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.assertj.core.util.Lists.newArrayList;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.times;
@@ -20,14 +18,6 @@ public class CombiningValidatorTest {
 	private static final String FIRST_ERROR_MESSAGE = "firstMessage";
 	private static final String SECOND_ERROR_MESSAGE = "secondMessage";
 
-	@Test
-	public void construct_emptyToken_() {
-		assertThatThrownBy(() -> {
-			new CombiningValidator<>(null);
-		}).isInstanceOf(IllegalArgumentException.class).hasMessageContaining("validators must not be null");
-
-	}
-
 	@Test
 	public void validate_containsNoValidators_validResult() {
 		Validator<Token> combiningValidator = new CombiningValidator<>(new ArrayList<>());
diff --git a/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/JwtTimestampValidatorTest.java b/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/JwtTimestampValidatorTest.java
index a4c43972..1021549e 100644
--- a/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/JwtTimestampValidatorTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/JwtTimestampValidatorTest.java
@@ -10,6 +10,7 @@ import java.time.Duration;
 import java.time.Instant;
 
 import static com.sap.cloud.security.TestConstants.*;
+import static com.sap.cloud.security.token.MockTokenBuilder.*;
 import static org.assertj.core.api.Assertions.assertThat;
 
 public class JwtTimestampValidatorTest {
@@ -24,12 +25,12 @@ public class JwtTimestampValidatorTest {
 	}
 
 	@Test
-	public void token_LacksExpiration_isValid() {
+	public void token_lacksExpiration_isNotValid() {
 		Token token = tokenFactory.withExpiration(null).build();
 
 		ValidationResult validationResult = cut.validate(token);
 
-		assertThat(validationResult.isValid()).isTrue();
+		assertThat(validationResult.isValid()).isFalse();
 	}
 
 	@Test
@@ -63,7 +64,7 @@ public class JwtTimestampValidatorTest {
 
 	@Test
 	public void tokenLacksNotBefore_isValid() {
-		Token token = tokenFactory.withNotBefore(null).build();
+		Token token = tokenFactory.withExpiration(NO_EXPIRE_DATE).withNotBefore(null).build();
 
 		ValidationResult validationResult = cut.validate(token);
 
@@ -72,7 +73,7 @@ public class JwtTimestampValidatorTest {
 
 	@Test
 	public void tokenNotBefore_now_isValid() {
-		Token token = tokenFactory.withNotBefore(NOW).build();
+		Token token = tokenFactory.withExpiration(NO_EXPIRE_DATE).withNotBefore(NOW).build();
 
 		ValidationResult validationResult = cut.validate(token);
 
@@ -81,7 +82,10 @@ public class JwtTimestampValidatorTest {
 
 	@Test
 	public void tokenNotBefore_insideClockSkewLeeway_isValid() {
-		Token token = tokenFactory.withNotBefore(NOW.plus(ONE_MINUTE).minus(ONE_SECOND)).build();
+		Token token = tokenFactory
+				.withExpiration(NO_EXPIRE_DATE)
+				.withNotBefore(NOW.plus(ONE_MINUTE).minus(ONE_SECOND))
+				.build();
 
 		ValidationResult validationResult = cut.validate(token);
 
@@ -91,7 +95,7 @@ public class JwtTimestampValidatorTest {
 	@Test
 	public void tokenNotBefore_afterClockSkewLeeway_isNotValidAndContainsErrorDescription() {
 		Instant inOneMinute = NOW.plus(ONE_MINUTE);
-		Token token = tokenFactory.withNotBefore(inOneMinute).build();
+		Token token = tokenFactory.withExpiration(NO_EXPIRE_DATE).withNotBefore(inOneMinute).build();
 
 		ValidationResult validationResult = cut.validate(token);
 
diff --git a/java-security/src/test/resources/xsuaaUserAccessTokenRSA256.txt b/java-security/src/test/resources/xsuaaUserAccessTokenRSA256.txt
index 17afd966..0638af6a 100644
--- a/java-security/src/test/resources/xsuaaUserAccessTokenRSA256.txt
+++ b/java-security/src/test/resources/xsuaaUserAccessTokenRSA256.txt
@@ -1,1 +1,1 @@
-eyJqa3UiOiJodHRwOi8vYXV0aC5jb20vdG9rZW5fa2V5cyIsImtpZCI6ImtleS1pZC0wIiwiYWxnIjoiUlMyNTYifQ==.eyJncmFudF90eXBlIjoidXNlcl90b2tlbiIsInVzZXJfbmFtZSI6InRlc3RVc2VyIiwib3JpZ2luIjoidXNlcklkcCIsImlzcyI6Imh0dHA6Ly9hdXRoLmNvbSIsImNpZCI6ImNsaWVudElkIn0=.mPvid5ZBGbxrS6eI5-vHmL8hdAvXhsYKmV3c8N8YWaQwUE03co2h9p6aHYj_SL-25dmOlc2tL12QtwKlHxbWv_JjFBUc53T4lMp1XKUR3kQKMawTDTLD0jTtYrxzMcWY2FzU4TyZmeeEutyqkv7JSMnFAvTvUQI5DOFwmeJiynM81OsDBZgkOJ0lb12kt1ymIYe1R5mbDUTz886cAbUXAz6Tr8CUX552LrAVrVotAwIy0508eUYOH5nfNAllhKT0Rq-FVK-UlWZ_jwVtpxlAam-NXAT7J_Wr4Ip71JeGCU_IoWCq1AGxEc64mb0-ygoenuImGYZELPEOAWzXiQ_cUQ==
\ No newline at end of file
+eyJqa3UiOiJodHRwOi8vYXV0aC5jb20vdG9rZW5fa2V5cyIsImtpZCI6ImtleS1pZC0wIiwiYWxnIjoiUlMyNTYifQ==.eyJncmFudF90eXBlIjoidXNlcl90b2tlbiIsInVzZXJfbmFtZSI6InRlc3RVc2VyIiwib3JpZ2luIjoidXNlcklkcCIsImlzcyI6Imh0dHA6Ly9hdXRoLmNvbSIsImV4cCI6IjY5NzQwMzE2MDAiLCJjaWQiOiJjbGllbnRJZCJ9.SnILMdkP_9IXKJH7TDRc3iGwck0SFEB63ZqoL7u5lthlvTrEx2jO1fYk8gjx9w45V3QKcyvV4gQ3xdOcuU-X-6eDn-aMo8WgaJwLAJhsvGtFvnlLU-hbMHiGCYrYb-b_lF13xS1-EJt_RE9ZehDuaIKNTulzKfcbzSle93ejGYsgYJsV8PPkjbV58xFUh-tQMDvAwobWyDHad7Y4x_myQDGnc1wqqcnO7nTEJI3Uui7m2TpVR4eKyZbsE-vjT1qXDCtfxSHYIehTg9DBw9dSMm5oZ9hwR0kXHGbtQypdh9GH99jHuU8BQER_sVuDdMhH6OWHdTakAisYZbYpiCBnOQ==
\ No newline at end of file