Repository: SAP/cloud-security-xsuaa-integration
Pull request #379 (2020-10-02 12:22:36)

SecurityTest extension
This PR adds a [junit 5 extension](https://junit.org/junit5/docs/current/user-guide/#extensions) for `SecurityTest`.


hassler-d: Waiting for #380 being merged.

Patch:
diff --git a/java-security/src/main/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapter.java b/java-security/src/main/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapter.java
index 28e2a851..f0a98662 100644
--- a/java-security/src/main/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapter.java
+++ b/java-security/src/main/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapter.java
@@ -267,7 +267,8 @@ public class XSUserInfoAdapter implements XSUserInfo {
 	 * Check if a token issued for another OAuth client has been forwarded to a
 	 * different client,
 	 *
-	 * This method does not support checking if the token can be accepted by Audience Validation.
+	 * This method does not support checking if the token can be accepted by
+	 * Audience Validation.
 	 *
 	 * @return true if token was forwarded or if it cannot be determined.
 	 */
diff --git a/pom.xml b/pom.xml
index 56b5307b..f9000c58 100644
--- a/pom.xml
+++ b/pom.xml
@@ -72,7 +72,7 @@
 		<javax.servlet.api.version>3.1.0</javax.servlet.api.version>
 		<mockwebserver.version>3.9.1</mockwebserver.version>
 		<junit.version>4.12</junit.version>
-		<junit-jupiter.version>5.6.2</junit-jupiter.version>
+		<junit-jupiter.version>5.7.0</junit-jupiter.version>
 		<hamcrest.version>1.3</hamcrest.version>
 		<mockito.version>2.23.0</mockito.version>
 		<assertj.version>3.13.2</assertj.version>
diff --git a/samples/java-security-usage/pom.xml b/samples/java-security-usage/pom.xml
index 2b3acd61..17cd015c 100755
--- a/samples/java-security-usage/pom.xml
+++ b/samples/java-security-usage/pom.xml
@@ -15,7 +15,7 @@
         <javax.servlet.api.version>3.1.0</javax.servlet.api.version>
         <assertj.version>3.13.2</assertj.version>
         <jetty.version>9.4.31.v20200723</jetty.version>
-        <junit.version>4.12</junit.version>
+        <junit.version>5.7.0</junit.version>
     </properties>
 
     <dependencies>
@@ -68,8 +68,8 @@
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>junit</groupId>
-            <artifactId>junit</artifactId>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-api</artifactId>
             <version>${junit.version}</version>
             <scope>test</scope>
         </dependency>
diff --git a/spring-xsuaa/Migration_JavaContainerSecurityProjects.md b/spring-xsuaa/Migration_JavaContainerSecurityProjects.md
index 33c5def9..e1a5be3b 100644
--- a/spring-xsuaa/Migration_JavaContainerSecurityProjects.md
+++ b/spring-xsuaa/Migration_JavaContainerSecurityProjects.md
@@ -37,7 +37,7 @@ which shows what had to be changed to migrate our open-SAP course application fr
 
 ## Maven Dependencies
 To use the new [spring-xsuaa](/spring-xsuaa) client library the dependencies declared in maven `pom.xml` need to be changed.
-See the [docs](/spring-xsuaa#configuration) on what to add to your `pom.xml`.
+See the [documentation](/spring-xsuaa#configuration) on what to add to your `pom.xml`.
 
 Now you are ready to **remove** the **`java-container-security`** client library by deleting the following dependencies from the pom.xml:
 
@@ -223,7 +223,7 @@ for more details.
 ## Testing
 In your unit test you might want to generate jwt tokens and have them validated. The new
 [java-security-test](/java-security-test) library provides it's own `JwtGenerator`. This can be embedded using the
-new `SecurityTestRule`. See the following snippet as example:
+new `SecurityTestRule` in Junit 4. See the following snippet as example:
 
 ```java
 @ClassRule
@@ -242,7 +242,7 @@ String jwt = securityTestRule.getPreconfiguredJwtGenerator()
     .getTokenValue();
 ```
 
-See the [java-security-test docs](/java-security-test) for more details.
+See the [java-security-test documenation](/java-security-test) for more details, also on how to leverage JUnit 5 extensions.
 
 ### Enable local testing
 For local testing you might need to provide custom `VCAP_SERVICES` before you run the application. 
@@ -254,8 +254,11 @@ under `xsuaa/credentials` for jwt validation:
 Before calling the service you need to provide a digitally signed JWT token to simulate that you are an authenticated user. 
 - Therefore simply set a breakpoint in `JwtGenerator.createToken()` and run your `JUnit` tests to fetch the value of `jwt` from there. In that case you can use the publicKey from `java-security-test`, like its done [here](/samples/localEnvironmentSetup.sh).
 
+
 Now you can test the service manually in the browser using the `Postman` chrome plugin and check whether the secured functions can be accessed when providing a valid generated Jwt Token.
 
+https://github.com/SAP/cloud-security-xsuaa-integration/blob/master/samples/localEnvironmentSetup.sh
+
 ## Things to check after migration 
 When your code compiles again you should first check that all your unit tests are running again. If you can test your
 application locally make sure that it is still working and finally test the application in cloud foundry.
diff --git a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaCredentials.java b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaCredentials.java
index 0b70c1c2..fb791cab 100644
--- a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaCredentials.java
+++ b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaCredentials.java
@@ -9,6 +9,7 @@ public class XsuaaCredentials {
 	private String url;
 	private String uaaDomain;
 	private String xsAppName;
+	private String verificationKey;
 
 	public String getClientId() {
 		return clientId;
@@ -49,4 +50,12 @@ public class XsuaaCredentials {
 	public void setXsAppName(String xsAppName) {
 		this.xsAppName = xsAppName;
 	}
+
+	public String getVerificationKey() {
+		return verificationKey;
+	}
+
+	public void setVerificationKey(String verificationKey) {
+		this.verificationKey = verificationKey;
+	}
 }
diff --git a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServiceConfigurationCustom.java b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServiceConfigurationCustom.java
index 6a55d4d9..57a22369 100644
--- a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServiceConfigurationCustom.java
+++ b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServiceConfigurationCustom.java
@@ -38,6 +38,6 @@ public class XsuaaServiceConfigurationCustom implements XsuaaServiceConfiguratio
 	@Nullable
 	@Override
 	public String getVerificationKey() {
-		return null;
+		return credentials.getVerificationKey();
 	}
 }
diff --git a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaAudienceValidator.java b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaAudienceValidator.java
index 5da2fe04..b4f45827 100644
--- a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaAudienceValidator.java
+++ b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaAudienceValidator.java
@@ -1,10 +1,11 @@
 package com.sap.cloud.security.xsuaa.token.authentication;
 
-import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.stream.Collectors;
+import java.util.Set;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -21,8 +22,8 @@ import com.sap.cloud.security.xsuaa.XsuaaServicesParser;
 import com.sap.cloud.security.xsuaa.token.TokenClaims;
 
 /**
- * Validate audience using audience field content. in case this field is empty,
- * the audience is derived from the scope field
+ * Validate audience using audience field content. In case this field is empty,
+ * the audience is derived from the scope field.
  */
 public class XsuaaAudienceValidator implements OAuth2TokenValidator<Jwt> {
 	private Map<String, String> appIdClientIdMap = new HashMap<>();
@@ -44,74 +45,68 @@ public class XsuaaAudienceValidator implements OAuth2TokenValidator<Jwt> {
 	public OAuth2TokenValidatorResult validate(Jwt token) {
 		String tokenClientId = token.getClaimAsString(TokenClaims.CLAIM_CLIENT_ID);
 		if (StringUtils.isEmpty(tokenClientId)) {
-			return OAuth2TokenValidatorResult.failure(new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT,
-					"Jwt token must contain 'cid' (client_id)", null));
+			return OAuth2TokenValidatorResult.failure(
+					new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT, "Jwt token must contain 'cid' (client_id)", null));
 		}
-		List<String> allowedAudiences = getAllowedAudiences(token);
+
+		final Set<String> allowedAudiences = getAllowedAudiences(token);
 
 		for (Map.Entry<String, String> xsuaaConfig : appIdClientIdMap.entrySet()) {
 			if (checkMatch(xsuaaConfig.getKey(), xsuaaConfig.getValue(), tokenClientId, allowedAudiences)) {
 				return OAuth2TokenValidatorResult.success();
 			}
 		}
-		String description = String.format("Jwt token with allowed audiences %s matches none of these: %s",
+
+		final String description = String.format("Jwt token with allowed audiences %s matches none of these: %s",
 				allowedAudiences, appIdClientIdMap.keySet().toString());
 		return OAuth2TokenValidatorResult.failure(new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT, description, null));
 	}
 
-	private boolean checkMatch(String appId, String clientId, String tokenClientId, List<String> allowedAudiences) {
+	private boolean checkMatch(String appId, String clientId, String tokenClientId, Set<String> allowedAudiences) {
 		// case 1 : token issued by own client (or master)
-		if (clientId.equals(tokenClientId)
-				|| (appId.contains("!b")
-						&& tokenClientId.endsWith("|" + appId))) {
+		if (clientId.equals(tokenClientId) || (appId.contains("!b") && tokenClientId.endsWith("|" + appId))) {
 			return true;
-		} else {
-			// case 2: foreign token
-			if (allowedAudiences.contains(appId)) {
-				return true;
-			} else {
-				return false;
-			}
 		}
+
+		// case 2: foreign token
+		return allowedAudiences.contains(appId);
 	}
 
 	/**
-	 * Retrieve audiences from token. In case the audience list is empty, take
+	 * Retrieve audiences from token. In case the audience list is empty, takes
 	 * audiences based on the scope names.
 	 *
 	 * @param token
-	 * @return (empty) list of audiences
+	 * @return (empty) set of audiences
 	 */
-	static List<String> getAllowedAudiences(Jwt token) {
-		List<String> allAudiences = new ArrayList<>();
-		List<String> tokenAudiences = token.getAudience();
+	static Set<String> getAllowedAudiences(Jwt token) {
+		final Set<String> allAudiences = new HashSet<>();
 
+		final List<String> tokenAudiences = token.getAudience();
 		if (tokenAudiences != null) {
 			for (String audience : tokenAudiences) {
-				if (audience.contains(".")) {
-					String aud = audience.substring(0, audience.indexOf('.'));
-					allAudiences.add(aud);
-				} else {
-					allAudiences.add(audience);
-				}
+				final String aud = audience.contains(".") ? audience.substring(0, audience.indexOf('.')) : audience;
+				allAudiences.add(aud);
 			}
 		}
 
 		// extract audience (app-id) from scopes
-		if (allAudiences.size() == 0) {
+		if (allAudiences.isEmpty()) {
 			for (String scope : getScopes(token)) {
 				if (scope.contains(".")) {
-					String aud = scope.substring(0, scope.indexOf('.'));
+					final String aud = scope.substring(0, scope.indexOf('.'));
 					allAudiences.add(aud);
 				}
 			}
 		}
-		return allAudiences.stream().distinct().filter(value -> !value.isEmpty()).collect(Collectors.toList());
+
+		allAudiences.remove("");
+
+		return allAudiences;
 	}
 
 	static List<String> getScopes(Jwt token) {
-		List<String> scopes = null;
-		scopes = token.getClaimAsStringList(TokenClaims.CLAIM_SCOPES);
-		return scopes != null ? scopes : new ArrayList<>();
+		List<String> scopes = token.getClaimAsStringList(TokenClaims.CLAIM_SCOPES);
+		return scopes != null ? scopes : Collections.emptyList();
 	}
 }
diff --git a/token-client/src/main/java/com/sap/cloud/security/xsuaa/tokenflows/PasswordTokenFlow.java b/token-client/src/main/java/com/sap/cloud/security/xsuaa/tokenflows/PasswordTokenFlow.java
index 9e23ac2f..4274ef89 100644
--- a/token-client/src/main/java/com/sap/cloud/security/xsuaa/tokenflows/PasswordTokenFlow.java
+++ b/token-client/src/main/java/com/sap/cloud/security/xsuaa/tokenflows/PasswordTokenFlow.java
@@ -48,8 +48,8 @@ public class PasswordTokenFlow {
 							username, password, subdomain, optionalParameters, disableCache);
 		} catch (OAuth2ServiceException e) {
 			throw new TokenFlowException(
-					String.format("Error requesting user token with grant_type 'client_credentials': %s",
-							e.getMessage()),
+					String.format("Error requesting user token with grant_type '%s': %s",
+							OAuth2TokenServiceConstants.GRANT_TYPE_PASSWORD, e.getMessage()),
 					e);
 		}
 	}


Test patch:
diff --git a/java-security-test/README.md b/java-security-test/README.md
index 4c55796c..1cd40aea 100644
--- a/java-security-test/README.md
+++ b/java-security-test/README.md
@@ -50,7 +50,7 @@ The `java-security-test` library uses third-party library [WireMock](http://wire
 Optionally, you can configure `java-security-test` to start an embedded Jetty servlet container that comes equipped with an [authenticator](src/main/java/com/sap/cloud/security/servlet/XsuaaTokenAuthenticator.java). The authenticator checks whether a request is done by an authenticated AND authorized party. You can also add your own servlets to the container. Only requests that contain a valid authorization header will be passed through to the servlet. See the following test code that triggers HTTP requests against the servlet container. One request does not contain the token inside the authorization header and is expected to result in HTTP `401` (unauthenticated). The other contains a valid token and is expected to succeed.
 
 
-#### JUnit 4
+### JUnit 4
 Use `SecurityTestRule`, which is an `ExternalResource` that set up `WireMock` and optionally the Jetty servlet container before a test, and guarantee to tear it down afterward.
 
 ```java
@@ -64,7 +64,7 @@ public class HelloJavaServletTest {
 	@ClassRule
 	public static SecurityTestRule rule = SecurityTestRule.getInstance(Service.XSUAA)
 			.useApplicationServer() // optionally customize application server, e.g. port
-			.addApplicationServlet(TestServlet.class, "/hi");  // add servlet to be tested to application server
+			.addApplicationServlet(HelloJavaServlet.class, "/hi");  // add servlet to be tested to application server
     
 	@After
 	public void tearDown() {
@@ -102,27 +102,68 @@ public class HelloJavaServletTest {
 	}
 }
 ```
-#### JUnit 5
-JUnit 5 does no longer support `Rule`. You can configure a static `SecurityTest` instance. The `setup` method starts `WireMock` and optionally the Jetty servlet container, if not yet running. Furthermore, it resets `WireMock` and reconfigures the stubs. Finally, call the `tearDown` method to stop the running server(s):
+
+
+### JUnit 5
+JUnit 5 does no longer support `Rule`. As of `java-security-test` version `2.8.0` you can implement using [JUnit 5 extensions](https://junit.org/junit5/docs/current/user-guide/#extensions) instead. 
+
+
+`XsuaaExtension` class as well as the `IasExtension` class implements the `BeforeAllCallback` to configure and start `WireMock` as mock server for the identity service. Furthermore, it implements the `AfterAllCallback` to stop the running server(s).
+
+#### Java sample
 
 ```java
+@ExtendWith(XsuaaExtension.class) 
 public class HelloJavaTest {
 
-	private static SecurityTest securityTest = new SecurityTest(Service.XSUAA);
-	
-	@BeforeEach
-	public void setup() throws Exception {
-		securityTest.setup();
+	@Test
+	public void sayHello(SecurityTestContext context) {
+		String jwt = context.getPreconfiguredJwtGenerator()
+						.withLocalScopes("Read")
+						.createToken().getTokenValue();
+		// call endpoint with Authorization header "Bearer <jwt>" 			
+		...
 	}
+}
+```
 
-	@AfterAll
-	public static void tearDown() {
-		securityTest.tearDown();
-	}
+Or in case you need to configure the ports or Jetty as servlet container, you can configure `SecurityTestExtension` and register it as extension using `@RegisterExtension` annotation.
+
+```java
+public class HelloJavaServletTest {
+    @RegisterExtension
+	static SecurityTestExtension extension = SecurityTestExtension.forService(Service.XSUAA)
+            .useApplicationServer() // optionally customize application server, e.g. port
+			.addApplicationServlet(HelloJavaServlet.class, "/hi");  // add servlet to be tested to application server
+
+	@Test
+    public void sayHello() {
+    		String jwt = extension.getContext().getPreconfiguredJwtGenerator()
+    						.withLocalScopes("Read")
+    						.createToken().getTokenValue();
+    		// call endpoint with Authorization header "Bearer <jwt>" 			
+    		...
+    }
+}
+```
+
+#### SpringBoot sample
+
+```java
+import static com.sap.cloud.security.test.SecurityTest.*;
+
+@SpringBootTest
+@AutoConfigureMockMvc
+@TestPropertySource(properties = { 
+		"xsuaa.uaadomain=" + DEFAULT_DOMAIN, 
+		"xsuaa.xsappname=" + DEFAULT_APP_ID,
+		"xsuaa.clientid=" + DEFAULT_CLIENT_ID })
+@ExtendWith(XsuaaExtension.class)
+public class HelloSpringTest {
 
 	@Test
-	public void v1_sayHello() throws Exception {
-		String jwt = securityTest.getPreconfiguredJwtGenerator()
+	public void sayHello(SecurityTestContext context) {
+		String jwt = context.getPreconfiguredJwtGenerator()
 						.withLocalScopes("Read")
 						.createToken().getTokenValue();
 		// call endpoint with Authorization header "Bearer <jwt>" 			
diff --git a/java-security-test/pom.xml b/java-security-test/pom.xml
index f9c64cff..3ed85c68 100644
--- a/java-security-test/pom.xml
+++ b/java-security-test/pom.xml
@@ -29,7 +29,10 @@
         <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
-            <version>${junit.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.junit.jupiter</groupId>
+			<artifactId>junit-jupiter-api</artifactId>
 		</dependency>
 		<dependency>
 			<groupId>org.eclipse.jetty</groupId>
@@ -88,7 +91,7 @@
 			<artifactId>slf4j-simple</artifactId>
 			<scope>test</scope>
 		</dependency>
-	</dependencies>
+    </dependencies>
 
 	<build>
 		<plugins>
diff --git a/java-security-test/src/main/java/com/sap/cloud/security/test/ApplicationServerConfiguration.java b/java-security-test/src/main/java/com/sap/cloud/security/test/ApplicationServerConfiguration.java
new file mode 100644
index 00000000..36d5c14b
--- /dev/null
+++ b/java-security-test/src/main/java/com/sap/cloud/security/test/ApplicationServerConfiguration.java
@@ -0,0 +1,71 @@
+package com.sap.cloud.security.test;
+
+import com.sap.cloud.security.config.Service;
+import org.eclipse.jetty.servlet.ServletHolder;
+
+import javax.servlet.Filter;
+import javax.servlet.Servlet;
+
+public interface ApplicationServerConfiguration {
+
+	/**
+	 * Specifies an embedded jetty as servlet server. It needs to be configured
+	 * before the test execution has started. The application server will be started
+	 * with default options for the given {@link Service}, see
+	 * {@link ApplicationServerOptions#forService(Service)} for details. By default
+	 * the servlet server will listen on a free random port. Use
+	 * {@link SecurityTestRule#useApplicationServer(ApplicationServerOptions)} to
+	 * overwrite default settings. Use {@code getApplicationServerUri()} to obtain
+	 * the actual port used at runtime.
+	 *
+	 * @return the rule itself.
+	 */
+	ApplicationServerConfiguration useApplicationServer();
+
+	/**
+	 * Specifies an embedded jetty as servlet server. It needs to be configured
+	 * before the test execution has started. Use
+	 * {@link ApplicationServerOptions#forService(Service)} to obtain a
+	 * configuration object that can be customized. See
+	 * {@link ApplicationServerOptions} for details.
+	 *
+	 * @param options
+	 *            custom options to configure the application server.
+	 * @return the rule itself.
+	 */
+	ApplicationServerConfiguration useApplicationServer(ApplicationServerOptions options);
+
+	/**
+	 * Adds a servlet to the servlet server. Only has an effect when used in
+	 * conjunction with {@link #useApplicationServer}.
+	 *
+	 * @param servletClass
+	 *            the servlet class that should be served.
+	 * @param path
+	 *            the path on which the servlet should be served, e.g. "/*".
+	 * @return the rule itself.
+	 */
+	ApplicationServerConfiguration addApplicationServlet(Class<? extends Servlet> servletClass, String path);
+
+	/**
+	 * Adds a servlet to the servlet server. Only has an effect when used in
+	 * conjunction with {@link #useApplicationServer}.
+	 *
+	 * @param servletHolder
+	 *            the servlet inside a {@link ServletHolder} that should be served.
+	 * @param path
+	 *            the path on which the servlet should be served, e.g. "/*".
+	 * @return the rule itself.
+	 */
+	ApplicationServerConfiguration addApplicationServlet(ServletHolder servletHolder, String path);
+
+	/**
+	 * Adds a filter to the servlet server. Only has an effect when used in
+	 * conjunction with {@link #useApplicationServer}.
+	 *
+	 * @param filterClass
+	 *            the filter class that should intercept with incoming requests.
+	 * @return the rule itself.
+	 */
+	ApplicationServerConfiguration addApplicationServletFilter(Class<? extends Filter> filterClass);
+}
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 36f999c8..7b494a39 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
@@ -108,10 +108,10 @@ public class JwtGenerator {
 	 * @return a new {@link JwtGenerator} instance.
 	 * @throws JsonParsingException
 	 *             if the file does not contain a valid json object
-	 * @throws IOException
+	 * @throws IllegalArgumentException
 	 *             if the given file cannot be read
 	 */
-	public static JwtGenerator getInstanceFromFile(Service service, String tokenJsonResource) throws IOException {
+	public static JwtGenerator getInstanceFromFile(Service service, String tokenJsonResource) {
 		return new JwtGenerator(service, JwtGenerator::calculateSignature).fromFile(tokenJsonResource);
 	}
 
diff --git a/java-security-test/src/main/java/com/sap/cloud/security/test/SecurityTest.java b/java-security-test/src/main/java/com/sap/cloud/security/test/SecurityTest.java
index 8560f83c..839dcebf 100644
--- a/java-security-test/src/main/java/com/sap/cloud/security/test/SecurityTest.java
+++ b/java-security-test/src/main/java/com/sap/cloud/security/test/SecurityTest.java
@@ -4,7 +4,6 @@ import com.github.tomakehurst.wiremock.WireMockServer;
 import com.sap.cloud.security.config.OAuth2ServiceConfigurationBuilder;
 import com.sap.cloud.security.config.Service;
 import com.sap.cloud.security.config.cf.VcapServicesParser;
-import com.sap.cloud.security.json.JsonParsingException;
 import com.sap.cloud.security.test.jetty.JettyTokenAuthenticator;
 import com.sap.cloud.security.token.Token;
 import com.sap.cloud.security.token.TokenClaims;
@@ -12,7 +11,6 @@ import com.sap.cloud.security.token.TokenHeader;
 import com.sap.cloud.security.xsuaa.client.OAuth2ServiceEndpointsProvider;
 import com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants;
 import com.sap.cloud.security.xsuaa.client.XsuaaDefaultEndpoints;
-import com.sap.cloud.security.xsuaa.http.HttpHeader;
 import com.sap.cloud.security.xsuaa.http.HttpHeaders;
 import com.sap.cloud.security.xsuaa.http.MediaType;
 import org.apache.commons.io.IOUtils;
@@ -45,7 +43,8 @@ import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options
 import static com.sap.cloud.security.config.cf.CFConstants.XSUAA.UAA_DOMAIN;
 import static com.sap.cloud.security.xsuaa.client.OidcConfigurationService.DISCOVERY_ENDPOINT_DEFAULT;
 
-public class SecurityTest {
+public class SecurityTest
+		implements SecurityTestContext, ServiceMockConfiguration, ApplicationServerConfiguration {
 
 	protected static final Logger LOGGER = LoggerFactory.getLogger(SecurityTest.class);
 
@@ -79,108 +78,43 @@ public class SecurityTest {
 		this.applicationServerOptions = ApplicationServerOptions.forService(service);
 	}
 
-	/**
-	 * Specifies an embedded jetty as servlet server. It needs to be configured
-	 * before the {@link #setup()} method. The application server will be started
-	 * with default options for the given {@link Service}, see
-	 * {@link ApplicationServerOptions#forService(Service)} for details. By default
-	 * the servlet server will listen on a free random port. Use
-	 * {@link SecurityTestRule#useApplicationServer(ApplicationServerOptions)} to
-	 * overwrite default settings. Use {@code getApplicationServerUri()} to obtain
-	 * the actual port used at runtime.
-	 *
-	 * @return the rule itself.
-	 */
+	@Override
 	public SecurityTest useApplicationServer() {
 		return useApplicationServer(ApplicationServerOptions.forService(service));
 	}
 
-	/**
-	 * Specifies an embedded jetty as servlet server. It needs to be configured
-	 * before the {@link #setup()} method. Use
-	 * {@link ApplicationServerOptions#forService(Service)} to obtain a
-	 * configuration object that can be customized. See
-	 * {@link ApplicationServerOptions} for details.
-	 *
-	 * @param applicationServerOptions
-	 *            custom options to configure the application server.
-	 * @return the rule itself.
-	 */
+	@Override
 	public SecurityTest useApplicationServer(ApplicationServerOptions applicationServerOptions) {
 		this.applicationServerOptions = applicationServerOptions;
 		useApplicationServer = true;
 		return this;
 	}
 
-	/**
-	 * Adds a servlet to the servlet server. Only has an effect when used in
-	 * conjunction with {@link #useApplicationServer}.
-	 *
-	 * @param servletClass
-	 *            the servlet class that should be served.
-	 * @param path
-	 *            the path on which the servlet should be served, e.g. "/*".
-	 * @return the rule itself.
-	 */
+	@Override
 	public SecurityTest addApplicationServlet(Class<? extends Servlet> servletClass, String path) {
 		applicationServletsByPath.put(path, new ServletHolder(servletClass));
 		return this;
 	}
 
-	/**
-	 * Adds a servlet to the servlet server. Only has an effect when used in
-	 * conjunction with {@link #useApplicationServer}.
-	 *
-	 * @param servletHolder
-	 *            the servlet inside a {@link ServletHolder} that should be served.
-	 * @param path
-	 *            the path on which the servlet should be served, e.g. "/*".
-	 * @return the rule itself.
-	 */
+	@Override
 	public SecurityTest addApplicationServlet(ServletHolder servletHolder, String path) {
 		applicationServletsByPath.put(path, servletHolder);
 		return this;
 	}
 
-	/**
-	 * Adds a filter to the servlet server. Only has an effect when used in
-	 * conjunction with {@link #useApplicationServer}.
-	 *
-	 * @param filterClass
-	 *            the filter class that should intercept with incoming requests.
-	 * @return the rule itself.
-	 */
+	@Override
 	public SecurityTest addApplicationServletFilter(Class<? extends Filter> filterClass) {
 		applicationServletFilters.add(new FilterHolder(filterClass));
 		return this;
 	}
 
-	/**
-	 * Overwrites the port on which the identity service mock server runs
-	 * (WireMock). It needs to be configured before the {@link #setup()} method. If
-	 * the port is not specified or is set to 0, a free random port is chosen.
-	 *
-	 * @param port
-	 *            the port on which the wire mock service is started.
-	 * @return the rule itself.
-	 */
+	@Override
 	public SecurityTest setPort(int port) {
 		wireMockServer = new WireMockServer(options().port(port));
 		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). Checked exceptions are caught and rethrown as runtime exceptions
-	 * for test convenience.
-	 *
-	 * @param publicKeyPath
-	 *            resource path to public key file.
-	 * @param privateKeyPath
-	 *            resource path to private key file.
-	 * @return the rule itself.
-	 */
+	@Override
 	public SecurityTest setKeys(String publicKeyPath, String privateKeyPath) {
 		try {
 			this.keys = RSAKeys.fromKeyFiles(publicKeyPath, privateKeyPath);
@@ -190,12 +124,7 @@ public class SecurityTest {
 		return this;
 	}
 
-	/**
-	 * This creates a JwtGenerator is fully configured as part of {@link #setup()}
-	 * method so that it can be used for testing.
-	 *
-	 * @return the preconfigured Jwt token generator
-	 */
+	@Override
 	public JwtGenerator getPreconfiguredJwtGenerator() {
 		JwtGenerator jwtGenerator = JwtGenerator.getInstance(service, clientId).withPrivateKey(keys.getPrivate());
 		if (jwksUrl == null || issuerUrl == null) {
@@ -211,79 +140,34 @@ public class SecurityTest {
 		return jwtGenerator.withClaimValue(TokenClaims.ISSUER, issuerUrl);
 	}
 
-	/**
-	 * This method creates an JwtGenerator that uses
-	 * {@link JwtGenerator#getInstanceFromFile(Service, String)} to provide a
-	 * {@link JwtGenerator} prefilled data contained in the
-	 * {@code tokenJsonResource} file. Some properties are overridden so that the
-	 * generated tokens can be used in unit tests.
-	 *
-	 * @param tokenJsonResource
-	 *            the resource path to the file containing the json file, see
-	 *            {@link JwtGenerator#getInstanceFromFile(Service, String)}
-	 * @return a new {@link JwtGenerator} instance
-	 * @throws IllegalArgumentException
-	 *             if the resource cannot be read
-	 * @throws JsonParsingException
-	 *             if the file contains invalid data
-	 */
-	public JwtGenerator getJwtGeneratorFromFile(String tokenJsonResource) throws IOException {
+	@Override
+	public JwtGenerator getJwtGeneratorFromFile(String tokenJsonResource) {
 		return JwtGenerator.getInstanceFromFile(service, tokenJsonResource)
 				.withHeaderParameter(TokenHeader.JWKS_URL, jwksUrl)
 				.withClaimValue(TokenClaims.ISSUER, issuerUrl)
 				.withPrivateKey(keys.getPrivate());
 	}
 
-	/**
-	 * Creates a {@link OAuth2ServiceConfigurationBuilder} prefilled with the data
-	 * from the classpath resource given by {@code configurationResourceName}. The
-	 * {@code url} of the configuration will be overridden with the url of the mock
-	 * server.
-	 *
-	 * @param configurationResourceName
-	 *            the name of classpath resource that contains the configuration
-	 *            json
-	 * @return a new {@link OAuth2ServiceConfigurationBuilder} instance
-	 * @throws IllegalArgumentException
-	 *             if the resource cannot be read
-	 * @throws JsonParsingException
-	 *             if the resource contains invalid data
-	 */
-	public OAuth2ServiceConfigurationBuilder getConfigurationBuilderFromFile(String configurationResourceName) {
+	@Override
+	public OAuth2ServiceConfigurationBuilder getOAuth2ServiceConfigurationBuilderFromFile(
+			String configurationResourceName) {
 		return VcapServicesParser.fromFile(configurationResourceName)
 				.getConfigurationBuilder()
 				.withProperty(UAA_DOMAIN, URI.create(issuerUrl).getHost())
 				.withUrl(issuerUrl);
 	}
 
-	/**
-	 * Creates a very basic token on base of the preconfigured Jwt token generator.
-	 * In case you like to specify further token claims, you can make use of
-	 * {@link #getPreconfiguredJwtGenerator()}
-	 *
-	 * @return the token.
-	 */
+	@Override
 	public Token createToken() {
 		return getPreconfiguredJwtGenerator().createToken();
 	}
 
-	/**
-	 * Allows to stub further endpoints of the identity service. Returns null if the
-	 * server is not yet initialized as part of {@link #setup()} method. You can
-	 * find a detailed explanation on how to configure wire mock here:
-	 * http://wiremock.org/docs/getting-started/
-	 *
-	 * @return an instance of WireMockServer
-	 */
+	@Override
 	public WireMockServer getWireMockServer() {
 		return wireMockServer;
 	}
 
-	/**
-	 * Returns the URI of the embedded jetty server or null if not specified.
-	 *
-	 * @return uri of the application server
-	 */
+	@Override
 	@Nullable
 	public String getApplicationServerUri() {
 		if (useApplicationServer) {
diff --git a/java-security-test/src/main/java/com/sap/cloud/security/test/SecurityTestContext.java b/java-security-test/src/main/java/com/sap/cloud/security/test/SecurityTestContext.java
new file mode 100644
index 00000000..5a0df289
--- /dev/null
+++ b/java-security-test/src/main/java/com/sap/cloud/security/test/SecurityTestContext.java
@@ -0,0 +1,82 @@
+package com.sap.cloud.security.test;
+
+import com.github.tomakehurst.wiremock.WireMockServer;
+import com.sap.cloud.security.config.OAuth2ServiceConfigurationBuilder;
+import com.sap.cloud.security.config.Service;
+import com.sap.cloud.security.json.JsonParsingException;
+import com.sap.cloud.security.token.Token;
+
+import javax.annotation.Nullable;
+
+public interface SecurityTestContext {
+
+	/**
+	 * This creates a JwtGenerator is fully configured as part of the {@code setup}
+	 * method so that it can be used for testing.
+	 *
+	 * @return the preconfigured Jwt token generator
+	 */
+	JwtGenerator getPreconfiguredJwtGenerator();
+
+	/**
+	 * This method creates an JwtGenerator that uses
+	 * {@link JwtGenerator#getInstanceFromFile(Service, String)} to provide a
+	 * {@link JwtGenerator} prefilled data contained in the
+	 * {@code tokenJsonResource} file. Some properties are overridden so that the
+	 * generated tokens can be used in unit tests.
+	 *
+	 * @param tokenJsonResource
+	 *            the resource path to the file containing the json file, see
+	 *            {@link JwtGenerator#getInstanceFromFile(Service, String)}
+	 * @return a new {@link JwtGenerator} instance
+	 * @throws IllegalArgumentException
+	 *             if the resource cannot be read
+	 * @throws JsonParsingException
+	 *             if the file contains invalid data
+	 */
+	JwtGenerator getJwtGeneratorFromFile(String tokenJsonResource);
+
+	/**
+	 * Creates a {@link OAuth2ServiceConfigurationBuilder} prefilled with the data
+	 * from the classpath resource given by {@code configurationResourceName}. The
+	 * {@code url} of the configuration will be overridden with the url of the mock
+	 * server.
+	 *
+	 * @param configurationResourceName
+	 *            the name of classpath resource that contains the configuration
+	 *            json
+	 * @return a new {@link OAuth2ServiceConfigurationBuilder} instance
+	 * @throws IllegalArgumentException
+	 *             if the resource cannot be read
+	 * @throws JsonParsingException
+	 *             if the resource contains invalid data
+	 */
+	OAuth2ServiceConfigurationBuilder getOAuth2ServiceConfigurationBuilderFromFile(String configurationResourceName);
+
+	/**
+	 * Creates a very basic token on base of the preconfigured Jwt token generator.
+	 * In case you like to specify further token claims, you can make use of
+	 * {@link #getPreconfiguredJwtGenerator()}
+	 *
+	 * @return the token.
+	 */
+	Token createToken();
+
+	/**
+	 * Allows to stub further endpoints of the identity service. You can find a
+	 * detailed explanation on how to configure wire mock here:
+	 * http://wiremock.org/docs/getting-started/
+	 *
+	 * @return an instance of WireMockServer
+	 */
+	WireMockServer getWireMockServer();
+
+	/**
+	 * Returns the URI of the embedded jetty server or null if it has not been
+	 * activated.
+	 *
+	 * @return uri of the application server
+	 */
+	@Nullable
+	String getApplicationServerUri();
+}
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 9e6933ba..89109641 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
@@ -11,9 +11,9 @@ import org.junit.rules.ExternalResource;
 import javax.annotation.Nullable;
 import javax.servlet.Filter;
 import javax.servlet.Servlet;
-import java.io.IOException;
 
-public class SecurityTestRule extends ExternalResource {
+public class SecurityTestRule extends ExternalResource
+		implements SecurityTestContext, ServiceMockConfiguration, ApplicationServerConfiguration {
 
 	public static final String DEFAULT_APP_ID = SecurityTest.DEFAULT_APP_ID;
 	public static final String DEFAULT_CLIENT_ID = SecurityTest.DEFAULT_CLIENT_ID;
@@ -39,108 +39,43 @@ public class SecurityTestRule extends ExternalResource {
 		return instance;
 	}
 
-	/**
-	 * Specifies an embedded jetty as servlet server. It needs to be configured
-	 * before the {@link #before()} method. The application server will be started
-	 * with default options for the given {@link Service}, see
-	 * {@link ApplicationServerOptions#forService(Service)} for details. By default
-	 * the servlet server will listen on a free random port. Use
-	 * {@link SecurityTestRule#useApplicationServer(ApplicationServerOptions)} to
-	 * overwrite default settings. Use {@link #getApplicationServerUri()} to obtain
-	 * the actual port used at runtime.
-	 *
-	 * @return the rule itself.
-	 */
+	@Override
 	public SecurityTestRule useApplicationServer() {
 		base.useApplicationServer();
 		return this;
 	}
 
-	/**
-	 * Specifies an embedded jetty as servlet server. It needs to be configured
-	 * before the {@link #before()} method. Use
-	 * {@link ApplicationServerOptions#forService(Service)} to obtain a
-	 * configuration object that can be customized. See
-	 * {@link ApplicationServerOptions} for details.
-	 *
-	 * @param applicationServerOptions
-	 *            custom options to configure the application server.
-	 * @return the rule itself.
-	 */
+	@Override
 	public SecurityTestRule useApplicationServer(ApplicationServerOptions applicationServerOptions) {
 		base.useApplicationServer(applicationServerOptions);
 		return this;
 	}
 
-	/**
-	 * Adds a servlet to the servlet server. Only has an effect when used in
-	 * conjunction with {@link #useApplicationServer}.
-	 *
-	 * @param servletClass
-	 *            the servlet class that should be served.
-	 * @param path
-	 *            the path on which the servlet should be served, e.g. "/*".
-	 * @return the rule itself.
-	 */
+	@Override
 	public SecurityTestRule addApplicationServlet(Class<? extends Servlet> servletClass, String path) {
 		base.addApplicationServlet(servletClass, path);
 		return this;
 	}
 
-	/**
-	 * Adds a servlet to the servlet server. Only has an effect when used in
-	 * conjunction with {@link #useApplicationServer}.
-	 *
-	 * @param servletHolder
-	 *            the servlet inside a {@link ServletHolder} that should be served.
-	 * @param path
-	 *            the path on which the servlet should be served, e.g. "/*".
-	 * @return the rule itself.
-	 */
+	@Override
 	public SecurityTestRule addApplicationServlet(ServletHolder servletHolder, String path) {
 		base.addApplicationServlet(servletHolder, path);
 		return this;
 	}
 
-	/**
-	 * Adds a filter to the servlet server. Only has an effect when used in
-	 * conjunction with {@link #useApplicationServer}.
-	 *
-	 * @param filterClass
-	 *            the filter class that should intercept with incoming requests.
-	 * @return the rule itself.
-	 */
+	@Override
 	public SecurityTestRule addApplicationServletFilter(Class<? extends Filter> filterClass) {
 		base.addApplicationServletFilter(filterClass);
 		return this;
 	}
 
-	/**
-	 * Overwrites the port on which the identity service mock server runs
-	 * (WireMock). It needs to be configured before the {@link #before()} method. If
-	 * the port is not specified or is set to 0, a free random port is chosen.
-	 *
-	 * @param port
-	 *            the port on which the wire mock service is started.
-	 * @return the rule itself.
-	 */
+	@Override
 	public SecurityTestRule setPort(int port) {
 		base.setPort(port);
 		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). Checked exceptions are caught and rethrown as runtime exceptions
-	 * for test convenience.
-	 *
-	 * @param publicKeyPath
-	 *            resource path to public key file.
-	 * @param privateKeyPath
-	 *            resource path to private key file.
-	 * @return the rule itself.
-	 */
+	@Override
 	public SecurityTestRule setKeys(String publicKeyPath, String privateKeyPath) {
 		base.setKeys(publicKeyPath, privateKeyPath);
 		return this;
@@ -151,45 +86,38 @@ public class SecurityTestRule extends ExternalResource {
 		base.setup(); // starts WireMock (to stub communication to identity service)
 	}
 
-	/**
-	 * For documentation see {@link SecurityTest#getPreconfiguredJwtGenerator()}.
-	 */
+	@Override
 	public JwtGenerator getPreconfiguredJwtGenerator() {
 		return base.getPreconfiguredJwtGenerator();
 	}
 
-	/**
-	 * For documentation see {@link SecurityTest#getJwtGeneratorFromFile(String)}.
-	 */
-	public JwtGenerator getJwtGeneratorFromFile(String tokenJsonResource) throws IOException {
+	@Override
+	public JwtGenerator getJwtGeneratorFromFile(String tokenJsonResource) {
 		return base.getJwtGeneratorFromFile(tokenJsonResource);
 	}
 
+	@Override
+	public OAuth2ServiceConfigurationBuilder getOAuth2ServiceConfigurationBuilderFromFile(String configurationJson) {
+		return base.getOAuth2ServiceConfigurationBuilderFromFile(configurationJson);
+	}
+
 	/**
-	 * For documentation see
-	 * {@link SecurityTest#getConfigurationBuilderFromFile(String)})}.
+	 * @deprecated use {@link #getOAuth2ServiceConfigurationBuilderFromFile(String)}
+	 *             instead.
 	 */
-	public OAuth2ServiceConfigurationBuilder getConfigurationBuilderFromFile(String tokenJsonResource) {
-		return base.getConfigurationBuilderFromFile(tokenJsonResource);
+	@Deprecated
+	public OAuth2ServiceConfigurationBuilder getConfigurationBuilderFromFile(String configurationJson) {
+		return base.getOAuth2ServiceConfigurationBuilderFromFile(configurationJson);
 	}
 
 	/**
-	 * This returns the {@link SecurityTest} which the {@link SecurityTestRule}
-	 * uses internally.
-	 *
-	 * @return the underlying {@link SecurityTest} instance.
+	 * @return the {@link SecurityTestContext} of this {@link SecurityTestRule}
 	 */
-	public SecurityTest getSecurityTest() {
+	public SecurityTestContext getContext() {
 		return base;
 	}
 
-	/**
-	 * Creates a very basic token on base of the preconfigured Jwt token generator.
-	 * In case you like to specify further token claims, you can make use of
-	 * {@link #getPreconfiguredJwtGenerator()}
-	 *
-	 * @return the token.
-	 */
+	@Override
 	public Token createToken() {
 		return base.createToken();
 	}
@@ -199,32 +127,19 @@ public class SecurityTestRule extends ExternalResource {
 	 *             WireMockServer is the base class of WireMockRule.
 	 *
 	 */
-	@Nullable
 	@Deprecated
 	public WireMockRule getWireMockRule() {
 		throw new UnsupportedOperationException(
 				"Deprecated since version 2.6.0. Please use getWireMockServer instead. WireMockServer is the base class of WireMockRule.");
 	}
 
-	/**
-	 * Allows to stub further endpoints of the identity service. Returns null if the
-	 * rule is not yet initialized as part of {@link #before()} method. You can find
-	 * a detailed explanation on how to configure wire mock here:
-	 * http://wiremock.org/docs/getting-started/
-	 *
-	 * @return an instance of WireMockRule or null.
-	 */
-	@Nullable
+	@Override
 	public WireMockServer getWireMockServer() {
 		return base.getWireMockServer();
 	}
 
-	/**
-	 * Returns the URI of the embedded jetty server or null if not specified.
-	 *
-	 * @return uri of the application server
-	 */
 	@Nullable
+	@Override
 	public String getApplicationServerUri() {
 		return base.getApplicationServerUri();
 	}
diff --git a/java-security-test/src/main/java/com/sap/cloud/security/test/ServiceMockConfiguration.java b/java-security-test/src/main/java/com/sap/cloud/security/test/ServiceMockConfiguration.java
new file mode 100644
index 00000000..7328f61b
--- /dev/null
+++ b/java-security-test/src/main/java/com/sap/cloud/security/test/ServiceMockConfiguration.java
@@ -0,0 +1,29 @@
+package com.sap.cloud.security.test;
+
+public interface ServiceMockConfiguration {
+
+	/**
+	 * Overwrites the port on which the identity service mock server runs
+	 * (WireMock). It needs to be configured before the test execution has started.
+	 * If the port is not specified or is set to 0, a free random port is chosen.
+	 *
+	 * @param port
+	 *            the port on which the wire mock service is started.
+	 * @return the rule itself.
+	 */
+	ServiceMockConfiguration setPort(int port);
+
+	/**
+	 * 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 exceptions
+	 * for test convenience.
+	 *
+	 * @param publicKeyPath
+	 *            resource path to public key file.
+	 * @param privateKeyPath
+	 *            resource path to private key file.
+	 * @return the rule itself.
+	 */
+	ServiceMockConfiguration setKeys(String publicKeyPath, String privateKeyPath);
+}
diff --git a/java-security-test/src/main/java/com/sap/cloud/security/test/extension/IasExtension.java b/java-security-test/src/main/java/com/sap/cloud/security/test/extension/IasExtension.java
new file mode 100644
index 00000000..9cf32086
--- /dev/null
+++ b/java-security-test/src/main/java/com/sap/cloud/security/test/extension/IasExtension.java
@@ -0,0 +1,10 @@
+package com.sap.cloud.security.test.extension;
+
+import com.sap.cloud.security.config.Service;
+
+public class IasExtension extends SecurityTestExtension {
+
+	public IasExtension() {
+		super(Service.IAS);
+	}
+}
diff --git a/java-security-test/src/main/java/com/sap/cloud/security/test/extension/SecurityTestExtension.java b/java-security-test/src/main/java/com/sap/cloud/security/test/extension/SecurityTestExtension.java
new file mode 100644
index 00000000..bef750e8
--- /dev/null
+++ b/java-security-test/src/main/java/com/sap/cloud/security/test/extension/SecurityTestExtension.java
@@ -0,0 +1,102 @@
+package com.sap.cloud.security.test.extension;
+
+import com.sap.cloud.security.config.Service;
+import com.sap.cloud.security.test.*;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.junit.jupiter.api.extension.*;
+
+import javax.servlet.Filter;
+import javax.servlet.Servlet;
+
+/**
+ * This class creates a
+ * <a href="https://junit.org/junit5/docs/current/user-guide/#extensions">junit
+ * extension</a> for {@link SecurityTest}.
+ */
+public class SecurityTestExtension implements ParameterResolver, BeforeAllCallback, AfterAllCallback,
+		ServiceMockConfiguration, ApplicationServerConfiguration {
+
+	private final SecurityTest securityTest;
+
+	SecurityTestExtension(Service service) {
+		this.securityTest = new SecurityTest(service);
+	}
+
+	SecurityTestExtension(SecurityTest securityTest) {
+		this.securityTest = securityTest;
+	}
+
+	public static SecurityTestExtension forService(Service service) {
+		return new SecurityTestExtension(new SecurityTest(service));
+	}
+
+	public SecurityTestContext getContext() {
+		return securityTest;
+	}
+
+	@Override
+	public void beforeAll(ExtensionContext context) throws Exception {
+		securityTest.setup();
+	}
+
+	@Override
+	public void afterAll(ExtensionContext context) {
+		securityTest.tearDown();
+	}
+
+	@Override
+	public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
+			throws ParameterResolutionException {
+		return parameterContext.getParameter().getType().equals(SecurityTestContext.class);
+	}
+
+	@Override
+	public SecurityTestContext resolveParameter(ParameterContext parameterContext,
+			ExtensionContext extensionContext)
+			throws ParameterResolutionException {
+		return securityTest;
+	}
+
+	@Override
+	public SecurityTestExtension setPort(int port) {
+		securityTest.setPort(port);
+		return this;
+	}
+
+	@Override
+	public SecurityTestExtension setKeys(String publicKeyPath, String privateKeyPath) {
+		securityTest.setKeys(publicKeyPath, privateKeyPath);
+		return this;
+	}
+
+	@Override
+	public SecurityTestExtension useApplicationServer() {
+		securityTest.useApplicationServer();
+		return this;
+	}
+
+	@Override
+	public SecurityTestExtension useApplicationServer(ApplicationServerOptions options) {
+		securityTest.useApplicationServer(options);
+		return this;
+	}
+
+	@Override
+	public SecurityTestExtension addApplicationServlet(Class<? extends Servlet> servletClass,
+			String path) {
+		securityTest.addApplicationServlet(servletClass, path);
+		return this;
+	}
+
+	@Override
+	public SecurityTestExtension addApplicationServlet(ServletHolder servletHolder, String path) {
+		securityTest.addApplicationServlet(servletHolder, path);
+		return this;
+	}
+
+	@Override
+	public SecurityTestExtension addApplicationServletFilter(Class<? extends Filter> filterClass) {
+		securityTest.addApplicationServletFilter(filterClass);
+		return this;
+	}
+}
diff --git a/java-security-test/src/main/java/com/sap/cloud/security/test/extension/XsuaaExtension.java b/java-security-test/src/main/java/com/sap/cloud/security/test/extension/XsuaaExtension.java
new file mode 100644
index 00000000..5ffbc45d
--- /dev/null
+++ b/java-security-test/src/main/java/com/sap/cloud/security/test/extension/XsuaaExtension.java
@@ -0,0 +1,10 @@
+package com.sap.cloud.security.test.extension;
+
+import com.sap.cloud.security.config.Service;
+
+public class XsuaaExtension extends SecurityTestExtension {
+
+	public XsuaaExtension() {
+		super(Service.XSUAA);
+	}
+}
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 90f78288..07b53119 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
@@ -153,8 +153,8 @@ public class SecurityTestRuleTest {
 	}
 
 	@Test
-	public void getSecurityTest() {
-		assertThat(cut.getSecurityTest()).isNotNull();
+	public void getContext() {
+		assertThat(cut.getContext()).isNotNull();
 	}
 
 	public static class SecurityTestRuleWithMockServlet {
diff --git a/java-security-test/src/test/java/com/sap/cloud/security/test/extension/IasExtensionTest.java b/java-security-test/src/test/java/com/sap/cloud/security/test/extension/IasExtensionTest.java
new file mode 100644
index 00000000..41f0096e
--- /dev/null
+++ b/java-security-test/src/test/java/com/sap/cloud/security/test/extension/IasExtensionTest.java
@@ -0,0 +1,18 @@
+package com.sap.cloud.security.test.extension;
+
+import com.sap.cloud.security.test.SecurityTestContext;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+@ExtendWith(IasExtension.class)
+public class IasExtensionTest {
+
+	@Test
+	void resolveSecurityTestConfigurationParameter(SecurityTestContext context) {
+		assertNotNull(context);
+		assertThat(context.getWireMockServer().isRunning()).isTrue();
+	}
+}
diff --git a/java-security-test/src/test/java/com/sap/cloud/security/test/extension/SecurityTestExtensionTest.java b/java-security-test/src/test/java/com/sap/cloud/security/test/extension/SecurityTestExtensionTest.java
new file mode 100644
index 00000000..c25cf538
--- /dev/null
+++ b/java-security-test/src/test/java/com/sap/cloud/security/test/extension/SecurityTestExtensionTest.java
@@ -0,0 +1,41 @@
+package com.sap.cloud.security.test.extension;
+
+import com.sap.cloud.security.test.ApplicationServerOptions;
+import com.sap.cloud.security.test.SecurityTestContext;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import java.net.URI;
+
+import static com.sap.cloud.security.config.Service.XSUAA;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.*;
+
+class SecurityTestExtensionTest {
+
+	static final int PORT = 4242;
+	static final int APPLICATION_SERVER_PORT = 2424;
+
+	@RegisterExtension
+	static SecurityTestExtension securityTestExtension = SecurityTestExtension.forService(XSUAA)
+			.setPort(PORT)
+			.useApplicationServer(ApplicationServerOptions.forService(XSUAA).usePort(APPLICATION_SERVER_PORT));
+
+	@Test
+	void isInitializedAndStartedWithCorrectSettings() {
+		SecurityTestContext context = securityTestExtension.getContext();
+
+		assertNotNull(context);
+		assertThat(context.getWireMockServer().port()).isEqualTo(PORT);
+		assertThat(URI.create(context.getApplicationServerUri()).getPort())
+				.isEqualTo(APPLICATION_SERVER_PORT);
+	}
+
+	@Test
+	void resolveSecurityTestConfigurationParameter(SecurityTestContext context) {
+		assertNotNull(context);
+		assertThat(context.getWireMockServer().port()).isEqualTo(PORT);
+		assertThat(URI.create(context.getApplicationServerUri()).getPort())
+				.isEqualTo(APPLICATION_SERVER_PORT);
+	}
+}
\ No newline at end of file
diff --git a/java-security-test/src/test/java/com/sap/cloud/security/test/extension/XsuaaExtensionTest.java b/java-security-test/src/test/java/com/sap/cloud/security/test/extension/XsuaaExtensionTest.java
new file mode 100644
index 00000000..a85d5c84
--- /dev/null
+++ b/java-security-test/src/test/java/com/sap/cloud/security/test/extension/XsuaaExtensionTest.java
@@ -0,0 +1,18 @@
+package com.sap.cloud.security.test.extension;
+
+import com.sap.cloud.security.test.SecurityTestContext;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+@ExtendWith(XsuaaExtension.class)
+public class XsuaaExtensionTest {
+
+	@Test
+	void resolveSecurityTestConfigurationParameter(SecurityTestContext context) {
+		assertNotNull(context);
+		assertThat(context.getWireMockServer().isRunning()).isTrue();
+	}
+}
\ No newline at end of file
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 3723f5b5..c476b1b2 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
@@ -1,6 +1,7 @@
 package com.sap.cloud.security.samples;
 
 import com.sap.cloud.security.test.SecurityTestRule;
+import com.sap.cloud.security.test.extension.SecurityTestExtension;
 import com.sap.cloud.security.token.SecurityContext;
 import com.sap.cloud.security.token.TokenClaims;
 import com.sap.cloud.security.xsuaa.http.HttpHeaders;
@@ -9,7 +10,9 @@ import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.impl.client.HttpClients;
 import org.apache.http.util.EntityUtils;
-import org.junit.*;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
 
 import java.io.IOException;
 
@@ -20,12 +23,11 @@ import static org.assertj.core.api.Assertions.assertThat;
 
 public class HelloJavaServletIntegrationTest {
 
-	@ClassRule
-	public static SecurityTestRule rule = SecurityTestRule.getInstance(XSUAA)
-			.useApplicationServer()
-			.addApplicationServlet(HelloJavaServlet.class, HelloJavaServlet.ENDPOINT);
+	@RegisterExtension
+	static SecurityTestExtension extension = SecurityTestExtension.forService(XSUAA)
+			.useApplicationServer().addApplicationServlet(HelloJavaServlet.class, HelloJavaServlet.ENDPOINT);
 
-	@After
+	@AfterEach
 	public void tearDown() {
 		SecurityContext.clearToken();
 	}
@@ -48,7 +50,7 @@ public class HelloJavaServletIntegrationTest {
 
 	@Test
 	public void requestWithValidTokenWithoutScopes_unauthorized() throws IOException {
-		String jwt = rule.getPreconfiguredJwtGenerator()
+		String jwt = extension.getContext().getPreconfiguredJwtGenerator()
 				.withClaimValue(GRANT_TYPE, GRANT_TYPE_CLIENT_CREDENTIALS)
 				.createToken()
 				.getTokenValue();
@@ -60,7 +62,7 @@ public class HelloJavaServletIntegrationTest {
 
 	@Test
 	public void requestWithValidToken_ok() throws IOException {
-		String jwt = rule.getPreconfiguredJwtGenerator()
+		String jwt = extension.getContext().getPreconfiguredJwtGenerator()
 				.withClaimValue(GRANT_TYPE, GRANT_TYPE_CLIENT_CREDENTIALS)
 				.withScopes(getGlobalScope("Read"))
 				.withClaimValue(TokenClaims.EMAIL, "tester@mail.com")
@@ -75,7 +77,7 @@ public class HelloJavaServletIntegrationTest {
 	}
 
 	private HttpGet createGetRequest(String accessToken) {
-		HttpGet httpGet = new HttpGet(rule.getApplicationServerUri() + HelloJavaServlet.ENDPOINT);
+		HttpGet httpGet = new HttpGet(extension.getContext().getApplicationServerUri() + HelloJavaServlet.ENDPOINT);
 		if(accessToken != null) {
 			httpGet.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken);
 		}
diff --git a/samples/spring-security-xsuaa-usage/src/test/java/sample/spring/xsuaa/junitjupiter/TestControllerTest.java b/samples/spring-security-xsuaa-usage/src/test/java/sample/spring/xsuaa/junitjupiter/TestControllerTest.java
index 0d2ddc6b..bb4e4a39 100644
--- a/samples/spring-security-xsuaa-usage/src/test/java/sample/spring/xsuaa/junitjupiter/TestControllerTest.java
+++ b/samples/spring-security-xsuaa-usage/src/test/java/sample/spring/xsuaa/junitjupiter/TestControllerTest.java
@@ -1,14 +1,15 @@
 package sample.spring.xsuaa.junitjupiter;
 
-
+import static com.sap.cloud.security.test.SecurityTest.*;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 
-import org.junit.Ignore;
-import org.junit.jupiter.api.AfterAll;
+import com.sap.cloud.security.test.SecurityTestContext;
+import com.sap.cloud.security.test.extension.XsuaaExtension;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
 import org.springframework.boot.test.context.SpringBootTest;
@@ -18,27 +19,24 @@ import org.springframework.test.context.TestPropertySource;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.request.RequestPostProcessor;
 
-import com.sap.cloud.security.config.Service;
-import com.sap.cloud.security.test.SecurityTest;
-
 @SpringBootTest
 @AutoConfigureMockMvc
-@TestPropertySource(properties = { "xsuaa.uaadomain=localhost", "xsuaa.xsappname=xsapp!t0815",
-		"xsuaa.clientid=sb-clientId!t0815" })
+@TestPropertySource(properties = {
+		"xsuaa.uaadomain=" + DEFAULT_DOMAIN,
+		"xsuaa.xsappname=" + DEFAULT_APP_ID,
+		"xsuaa.clientid=" + DEFAULT_CLIENT_ID })
+@ExtendWith(XsuaaExtension.class)
 public class TestControllerTest {
 
 	@Autowired
 	private MockMvc mvc;
 
-	private static SecurityTest securityTest = new SecurityTest(Service.XSUAA);
-
 	private String jwt;
 
 	private String jwtAdmin;
 
 	@BeforeEach
-	public void setup() throws Exception {
-		securityTest.setup();
+	public void setup(SecurityTestContext securityTest) {
 		jwt = securityTest.getPreconfiguredJwtGenerator()
 				.withLocalScopes("Read")
 				.createToken().getTokenValue();
@@ -47,11 +45,6 @@ public class TestControllerTest {
 				.createToken().getTokenValue();
 	}
 
-	@AfterAll
-	public static void tearDown() {
-		securityTest.tearDown();
-	}
-
 	@Test
 	public void v1_sayHello() throws Exception {
 		String response = mvc.perform(get("/v1/sayHello").with(bearerToken(jwtAdmin)))
@@ -94,7 +87,7 @@ public class TestControllerTest {
 	}
 
 	@Test
-	public void v1_accessSensitiveData_Forbidden() throws Exception {
+	public void v1_accessSensitiveData_Forbidden(SecurityTestContext securityTest) throws Exception {
 		String jwtNoScopes = securityTest.getPreconfiguredJwtGenerator()
 				.createToken().getTokenValue();
 
diff --git a/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaAudienceValidatorTest.java b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaAudienceValidatorTest.java
index 344f3ca6..cb7de9e8 100644
--- a/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaAudienceValidatorTest.java
+++ b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaAudienceValidatorTest.java
@@ -7,6 +7,7 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
+import java.util.Set;
 
 import org.junit.Assert;
 import org.junit.Before;
@@ -67,8 +68,7 @@ public class XsuaaAudienceValidatorTest {
 	public void testExtractAudiencesFromTokenScopes() {
 		Jwt token = new JwtGenerator()
 				.addScopes("test1!t1.read", "test2!t1.read", "test2!t1.write", ".scopeWithoutAppId").getToken();
-		List<String> audiences = new XsuaaAudienceValidator(serviceConfigurationSameClientId)
-				.getAllowedAudiences(token);
+		Set<String> audiences = XsuaaAudienceValidator.getAllowedAudiences(token);
 		Assert.assertThat(audiences.size(), is(2));
 		Assert.assertThat(audiences, hasItem("test1!t1"));
 		Assert.assertThat(audiences, hasItem("test2!t1"));