Repository: SAP/cloud-security-xsuaa-integration
Pull request #272 (2020-04-28 10:46:25)

Sample and Documentation: IAS identity kernel service
### Overview
This sample is a Java back-end application running on the Cloud Foundry. For all incoming requests it checks whether the IAS token is valid. 
Technically, the user is authenticated using the 
[`IasTokenAuthenticator`](/java-security/src/main/java/com/sap/cloud/security/servlet) which is defined in the [Java Security](../../java-security/) library.

### 2020-20-12
The name of the identity service will be "identity" and the plan name is "application".



Patch:
diff --git a/java-api/src/main/java/com/sap/cloud/security/config/Service.java b/java-api/src/main/java/com/sap/cloud/security/config/Service.java
index 37a5b0ea..ad785927 100644
--- a/java-api/src/main/java/com/sap/cloud/security/config/Service.java
+++ b/java-api/src/main/java/com/sap/cloud/security/config/Service.java
@@ -1,5 +1,6 @@
 package com.sap.cloud.security.config;
 
+import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
@@ -10,8 +11,11 @@ public enum Service {
 	XSUAA("xsuaa"), IAS(getIasServiceName());
 
 	private static String getIasServiceName() {
-		String iasServiceName = System.getenv("IAS_SERVICE_NAME"); // TODO as of now its "identity-beta"
-		return iasServiceName;
+		Logger logger = LoggerFactory.getLogger(Service.class);
+		if(System.getenv("IAS_SERVICE_NAME") != null) {
+			logger.warn("As of version 2.8.0 IAS_SERVICE_NAME system environment variable is no longer needed. Service 'identity' is available with plan 'application'.");
+		}
+		return "identity";
 	}
 
 	private final String cloudFoundryName;
@@ -27,9 +31,6 @@ public enum Service {
 	 * @return name of the identity service in context of Cloud Foundry environment.
 	 */
 	public String getCFName() {
-		if (this == IAS && cloudFoundryName == null) {
-			LoggerFactory.getLogger(Service.class).info("IAS Service is not yet supported!!!"); // TODO remove
-		}
 		return cloudFoundryName;
 	}
 }
diff --git a/java-api/src/main/java/com/sap/cloud/security/token/TokenClaims.java b/java-api/src/main/java/com/sap/cloud/security/token/TokenClaims.java
index 9f0f457b..a49cb666 100644
--- a/java-api/src/main/java/com/sap/cloud/security/token/TokenClaims.java
+++ b/java-api/src/main/java/com/sap/cloud/security/token/TokenClaims.java
@@ -43,7 +43,6 @@ public final class TokenClaims {
 		public static final String EXTERNAL_ATTRIBUTE_ZDN = "zdn"; // subdomain
 		// new with SECAUTH-806
 		public static final String EXTERNAL_ATTRIBUTE_SUBACCOUNTID = "subaccountid";
-		@Deprecated
 		public static final String EXTERNAL_ATTRIBUTE_ENHANCER = "enhancer";
 		public static final String XS_USER_ATTRIBUTES = "xs.user.attributes";
 		public static final String TRUSTED_CLIENT_ID_SUFFIX = "trustedclientidsuffix";
diff --git a/java-security/src/main/java/com/sap/cloud/security/adapter/spring/SAPOfflineTokenServicesCloud.java b/java-security/src/main/java/com/sap/cloud/security/adapter/spring/SAPOfflineTokenServicesCloud.java
index 60d33e3b..f6c599f0 100644
--- a/java-security/src/main/java/com/sap/cloud/security/adapter/spring/SAPOfflineTokenServicesCloud.java
+++ b/java-security/src/main/java/com/sap/cloud/security/adapter/spring/SAPOfflineTokenServicesCloud.java
@@ -184,7 +184,8 @@ public class SAPOfflineTokenServicesCloud implements ResourceServerTokenServices
 		final AuthorizationRequest authorizationRequest = new AuthorizationRequest(clientId, scopes);
 		authorizationRequest.setAuthorities(createAuthorities(scopes));
 		authorizationRequest.setApproved(true);
-		return new OAuth2Authentication(authorizationRequest.createOAuth2Request(), userAuthentication);
+		OAuth2Authentication authn = new OAuth2Authentication(authorizationRequest.createOAuth2Request(), userAuthentication);
+		return authn;
 	}
 
 	@Nullable
@@ -247,10 +248,9 @@ public class SAPOfflineTokenServicesCloud implements ResourceServerTokenServices
 			switch (serviceConfiguration.getService()) {
 			case XSUAA:
 				return new XsuaaToken(accessToken).withScopeConverter(xsuaaScopeConverter);
-			case IAS:
-				return new SapIdToken(accessToken);
+			//case IAS: // TODO Support IAS only in case of multiple feature requests.
+			//	return new SapIdToken(accessToken);
 			default:
-				// TODO support IAS
 				throw new InvalidTokenException(
 						"AccessToken of service " + serviceConfiguration.getService() + " is not supported.");
 			}
diff --git a/java-security/src/main/java/com/sap/cloud/security/adapter/spring/SpringSecurityContext.java b/java-security/src/main/java/com/sap/cloud/security/adapter/spring/SpringSecurityContext.java
index 697bfb32..65fc0ee1 100644
--- a/java-security/src/main/java/com/sap/cloud/security/adapter/spring/SpringSecurityContext.java
+++ b/java-security/src/main/java/com/sap/cloud/security/adapter/spring/SpringSecurityContext.java
@@ -1,10 +1,6 @@
 package com.sap.cloud.security.adapter.spring;
 
-import com.sap.cloud.security.token.AccessToken;
-import com.sap.cloud.security.token.Token;
-import com.sap.cloud.security.token.XsuaaToken;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import com.sap.cloud.security.token.*;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.authority.SimpleGrantedAuthority;
@@ -45,8 +41,11 @@ public class SpringSecurityContext {
 				authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
 			OAuth2AuthenticationDetails authDetails = (OAuth2AuthenticationDetails) authentication.getDetails();
 			String tokenValue = authDetails.getTokenValue();
-			// TODO IAS Support
-			return new XsuaaTokenWithGrantedAuthorities(tokenValue, authentication.getAuthorities());
+			AbstractToken xsuaaToken = new XsuaaTokenWithGrantedAuthorities(tokenValue, authentication.getAuthorities());
+			if (xsuaaToken.isXsuaaToken()) {
+				return xsuaaToken;
+			}
+			return new SapIdToken(tokenValue);
 		}
 		return null;
 	}
diff --git a/java-security/src/main/java/com/sap/cloud/security/token/AbstractToken.java b/java-security/src/main/java/com/sap/cloud/security/token/AbstractToken.java
index a5d63ce3..46f3098b 100644
--- a/java-security/src/main/java/com/sap/cloud/security/token/AbstractToken.java
+++ b/java-security/src/main/java/com/sap/cloud/security/token/AbstractToken.java
@@ -14,7 +14,10 @@ import java.security.Principal;
 import java.time.Instant;
 import java.time.LocalDateTime;
 import java.time.ZoneOffset;
-import java.util.*;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
 import java.util.regex.Pattern;
 
 import static com.sap.cloud.security.token.TokenClaims.*;
@@ -112,10 +115,20 @@ public abstract class AbstractToken implements Token {
 	@Override
 	public Set<String> getAudiences() {
 		Set<String> audiences = new LinkedHashSet<>();
-		audiences.addAll(getClaimAsStringList(AUDIENCE));
+		audiences.addAll(getClaimAsStringList(TokenClaims.AUDIENCE));
 		return audiences;
 	}
 
+	public boolean isXsuaaToken() {
+		if (tokenBody.contains(EXTERNAL_ATTRIBUTE)) {
+			JsonObject externalAttributes = tokenBody.getJsonObject(EXTERNAL_ATTRIBUTE);
+			if ("XSUAA".equals(externalAttributes.getAsString(EXTERNAL_ATTRIBUTE_ENHANCER))) {
+				return true;
+			}
+		}
+		return false;
+	}
+
 	protected Principal createPrincipalByName(String name) {
 		return new Principal() {
 			@Override
diff --git a/samples/java-security-usage-ias/README.md b/samples/java-security-usage-ias/README.md
new file mode 100644
index 00000000..90eafb35
--- /dev/null
+++ b/samples/java-security-usage-ias/README.md
@@ -0,0 +1,66 @@
+# Description
+This sample is a Java back-end application running on the Cloud Foundry. For all incoming requests it checks whether the user is authorized using the 
+[`IasTokenAuthenticator`](/java-security/src/main/java/com/sap/cloud/security/servlet) which is defined in the [Java Security](../../java-security/) library.
+
+# Deployment on Cloud Foundry
+To deploy the application, the following steps are required:
+- Compile the Java application
+- Create a ias service instance
+- Configure the manifest
+- Deploy the application    
+- Access the application
+
+## Compile the Java application
+Run maven to package the application
+```shell
+mvn clean package
+```
+
+## Create the ias service instance
+Use the ias service broker and create a service instance (don't forget to replace the placeholders)
+```shell
+cf create-service identity application ias-java-security
+```
+
+## Configure the manifest
+The [vars](../vars.yml) contains hosts and paths that need to be adopted.
+
+## Deploy the application
+Deploy the application using cf push. It will expect 1 GB of free memory quota.
+
+```shell
+cf push --vars-file ../vars.yml
+```
+
+## Access the application
+- Get an access token via `curl`. Make sure that you replace the placeholders `clientid`, `clientsecret` and `url` (without `https://` !!!) according to the service configuration that are stored as system environment variable `VCAP_SERVICES.identity.credentials`. You can get them using `cf env java-security-usage-ias`. 
+
+```
+curl -X POST \
+  https://<<clientid>>:<<clientsecret>>@<<url>>/oauth2/token \
+  -H 'Content-Type: application/x-www-form-urlencoded' \
+  -d 'grant_type=password&username=<<your ias user>>&password=<<your ias password>>'
+```
+
+Copy the `id_token` into your clipboard.
+
+- Access the app via `curl`. Don't forget to fill the placeholders.
+```
+curl -X GET \
+  https://java-security-usage-ias-<<ID>>.<<LANDSCAPE_APPS_DOMAIN>>/hello-java-security-ias \
+  -H 'Authorization: Bearer <<your id_token>>'
+```
+
+You should see something like this:
+```
+You ('<your email>') are authenticated and can access the application.
+```
+- If you call the same endpoint without `Authorization` header you should get a `401`.
+
+## Clean-Up
+Finally delete your application and your service instances using the following commands:
+```
+cf us java-security-usage-ias ias-java-security
+cf delete -f java-security-usage-ias
+cf delete-service -f ias-java-security
+```
diff --git a/samples/java-security-usage-ias/manifest.yml b/samples/java-security-usage-ias/manifest.yml
new file mode 100644
index 00000000..2a57d1bc
--- /dev/null
+++ b/samples/java-security-usage-ias/manifest.yml
@@ -0,0 +1,17 @@
+---
+# Configuration:
+# configured for EU10. For other landscapes, please adopt the destination in ../vars.yml
+# If the routes are occupied, you might need to change the host in java-tokenclient-usage and the url in the destination
+applications:
+- name: java-security-usage-ias
+  instances: 1
+  memory: 896M
+  routes:
+    - route: java-security-usage-ias-((ID)).((LANDSCAPE_APPS_DOMAIN))
+  host: 
+  path: target/java-security-usage-ias.war
+  buildpacks:
+    - java_buildpack
+  services:
+    - ias-java-security
+...
diff --git a/samples/java-security-usage-ias/pom.xml b/samples/java-security-usage-ias/pom.xml
new file mode 100755
index 00000000..72eea1f7
--- /dev/null
+++ b/samples/java-security-usage-ias/pom.xml
@@ -0,0 +1,92 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.sap.cloud.security.xssec.samples</groupId>
+    <artifactId>java-security-usage-ias</artifactId>
+    <version>2.7.8</version>
+    <packaging>war</packaging>
+
+    <!--profiles>
+        <profile>
+            <id>allow-snapshots</id>
+            <activation><activeByDefault>true</activeByDefault></activation>
+            <repositories>
+                <repository>
+                    <id>snapshots-repo</id>
+                    <url>https://oss.sonatype.org/content/repositories/snapshots</url>
+                    <releases><enabled>false</enabled></releases>
+                    <snapshots><enabled>true</enabled></snapshots>
+                </repository>
+            </repositories>
+        </profile>
+    </profiles-->
+
+    <properties>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+        <sap.cloud.security.version>2.7.8</sap.cloud.security.version>
+        <slf4j.api.version>1.7.30</slf4j.api.version>
+        <apache.httpclient.version>4.5.9</apache.httpclient.version>
+        <javax.servlet.api.version>3.1.0</javax.servlet.api.version>
+        <wiremock.version>2.25.1</wiremock.version>
+        <assertj.version>3.13.2</assertj.version>
+        <junit.version>5.7.0</junit.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>${apache.httpclient.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+            <version>${javax.servlet.api.version}</version>
+            <scope>compile</scope>
+        </dependency>
+        <!-- logging -->
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <version>${slf4j.api.version}</version>
+            <scope>compile</scope>
+        </dependency>
+        <!-- security -->
+        <dependency>
+            <groupId>com.sap.cloud.security</groupId>
+            <artifactId>java-security</artifactId>
+            <version>${sap.cloud.security.version}</version>
+        </dependency>
+
+        <!-- test utilities -->
+        <dependency>
+            <groupId>com.sap.cloud.security</groupId>
+            <artifactId>java-security-test</artifactId>
+            <version>${sap.cloud.security.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-api</artifactId>
+            <version>${junit.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+            <version>${assertj.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+    </dependencies>
+    <build>
+        <finalName>${project.artifactId}</finalName>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/samples/java-security-usage-ias/src/main/java/com/sap/cloud/security/samples/ias/HelloJavaServlet.java b/samples/java-security-usage-ias/src/main/java/com/sap/cloud/security/samples/ias/HelloJavaServlet.java
new file mode 100644
index 00000000..53d46528
--- /dev/null
+++ b/samples/java-security-usage-ias/src/main/java/com/sap/cloud/security/samples/ias/HelloJavaServlet.java
@@ -0,0 +1,37 @@
+package com.sap.cloud.security.samples.ias;
+
+import com.sap.cloud.security.token.SecurityContext;
+import com.sap.cloud.security.token.Token;
+import com.sap.cloud.security.token.TokenClaims;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+@WebServlet(HelloJavaServlet.ENDPOINT)
+public class HelloJavaServlet extends HttpServlet {
+	static final String ENDPOINT = "/hello-java-security-ias";
+	private static final long serialVersionUID = 1L;
+	private static Logger logger = LoggerFactory.getLogger(HelloJavaServlet.class);
+
+	/**
+	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
+	 */
+	@Override
+	protected void doGet(HttpServletRequest request, HttpServletResponse response) {
+		response.setContentType("text/plain");
+		Token token = SecurityContext.getToken();
+		try {
+			response.getWriter().write("You ('"
+					+ token.getClaimAsString(TokenClaims.EMAIL) + "') "
+					+ "are authenticated and can access the application.");
+		} catch (final IOException e) {
+			logger.error("Failed to write error response: " + e.getMessage() + ".", e);
+		}
+	}
+
+}
diff --git a/samples/java-security-usage-ias/src/main/java/com/sap/cloud/security/samples/ias/IasSecurityFilter.java b/samples/java-security-usage-ias/src/main/java/com/sap/cloud/security/samples/ias/IasSecurityFilter.java
new file mode 100644
index 00000000..36d20c6b
--- /dev/null
+++ b/samples/java-security-usage-ias/src/main/java/com/sap/cloud/security/samples/ias/IasSecurityFilter.java
@@ -0,0 +1,53 @@
+package com.sap.cloud.security.samples.ias;
+
+import com.sap.cloud.security.servlet.IasTokenAuthenticator;
+import com.sap.cloud.security.servlet.TokenAuthenticationResult;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.*;
+import javax.servlet.annotation.WebFilter;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+@WebFilter("/*") // filter for any endpoint
+public class IasSecurityFilter implements Filter {
+	private static final Logger LOGGER = LoggerFactory.getLogger(IasSecurityFilter.class);
+	private final IasTokenAuthenticator iasTokenAuthenticator;
+
+	public IasSecurityFilter() {
+		iasTokenAuthenticator = new IasTokenAuthenticator();
+	}
+
+	@Override
+	public void init(FilterConfig filterConfig) {
+	}
+
+	@Override
+	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+			throws IOException, ServletException {
+		TokenAuthenticationResult authenticationResult = iasTokenAuthenticator.validateRequest(request, response);
+		if (authenticationResult.isAuthenticated()) {
+			LOGGER.debug("AUTHENTICATED");
+			chain.doFilter(request, response) ;
+		} else {
+			LOGGER.debug("UNAUTHENTICATED");
+			sendUnauthenticatedResponse(response, authenticationResult.getUnauthenticatedReason());
+		}
+	}
+
+	private void sendUnauthenticatedResponse(ServletResponse response, String unauthenticatedReason)  {
+		if (response instanceof HttpServletResponse) {
+			try {
+				HttpServletResponse httpServletResponse = (HttpServletResponse) response;
+				httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, unauthenticatedReason); // 401
+			} catch (IOException e) {
+				LOGGER.error("Failed to send error response", e);
+			}
+		}
+	}
+
+	@Override
+	public void destroy() {
+	}
+}
diff --git a/samples/java-security-usage-ias/src/main/webapp/WEB-INF/web.xml b/samples/java-security-usage-ias/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 00000000..0032feca
--- /dev/null
+++ b/samples/java-security-usage-ias/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://java.sun.com/xml/ns/javaee"
+         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+         version="2.5">
+</web-app>
\ No newline at end of file


Test patch:
diff --git a/java-api/src/test/java/com/sap/cloud/security/config/ServiceTest.java b/java-api/src/test/java/com/sap/cloud/security/config/ServiceTest.java
index ef1b25a5..7bf34aad 100644
--- a/java-api/src/test/java/com/sap/cloud/security/config/ServiceTest.java
+++ b/java-api/src/test/java/com/sap/cloud/security/config/ServiceTest.java
@@ -6,13 +6,9 @@ import static org.assertj.core.api.Assertions.*;
 
 public class ServiceTest {
 
-	/**
-	 * This test assumes that the system variable "IAS_SERVICE_NAME" is not set. It
-	 * should be removed as soon as {@link Service#IAS} is supported.
-	 */
 	@Test
-	public void getCFNameOfIasWhenEnvironmentVariableIsNotSet_shouldReturnNull() {
-		assertThat(Service.IAS.getCFName()).isNull();
+	public void getCFNameOfIas_shouldReturnCorrectName() {
+		assertThat(Service.IAS.getCFName()).isEqualTo("identity");
 	}
 
 	@Test
diff --git a/java-security-it/src/test/resources/ias-simple/vcap_services-single.json b/java-security-it/src/test/resources/ias-simple/vcap_services-single.json
index 100f8d6b..8462b5b4 100644
--- a/java-security-it/src/test/resources/ias-simple/vcap_services-single.json
+++ b/java-security-it/src/test/resources/ias-simple/vcap_services-single.json
@@ -1,5 +1,5 @@
 {
-  "identity-beta": [
+  "identity": [
     {
       "binding_name": null,
       "credentials": {
@@ -8,9 +8,9 @@
         "url": "https://myauth.com"
       },
       "instance_name": "myservice",
-      "label": "identity-beta",
+      "label": "identity",
       "name": "myservice",
-      "plan": "default",
+      "plan": "application",
       "provider": null,
       "syslog_drain_url": null,
       "tags": [],
diff --git a/java-security-it/src/test/resources/vcap_services-multiple.json b/java-security-it/src/test/resources/vcap_services-multiple.json
index 1b65bd36..79cab892 100644
--- a/java-security-it/src/test/resources/vcap_services-multiple.json
+++ b/java-security-it/src/test/resources/vcap_services-multiple.json
@@ -22,7 +22,7 @@
     "volume_mounts": []
    }
   ],
-  "identity-beta": [
+  "identity": [
   {
    "binding_name": null,
    "credentials": {
@@ -31,9 +31,9 @@
     "url": "http://localhost"
    },
    "instance_name": "myservice",
-   "label": "identity-beta",
+   "label": "identity",
    "name": "myservice",
-   "plan": "default",
+   "plan": "application",
    "provider": null,
    "syslog_drain_url": null,
    "tags": [],
diff --git a/java-security/src/test/java/com/sap/cloud/security/adapter/spring/SAPOfflineTokenServicesCloudTest.java b/java-security/src/test/java/com/sap/cloud/security/adapter/spring/SAPOfflineTokenServicesCloudTest.java
index 9cacc048..c31007de 100644
--- a/java-security/src/test/java/com/sap/cloud/security/adapter/spring/SAPOfflineTokenServicesCloudTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/adapter/spring/SAPOfflineTokenServicesCloudTest.java
@@ -91,7 +91,7 @@ public class SAPOfflineTokenServicesCloudTest {
 	}
 
 	@Test
-	public void loadAuthentication_ias() {
+	public void loadAuthentication_iasToken_throwsException() {
 		OAuth2ServiceConfiguration configuration = OAuth2ServiceConfigurationBuilder
 				.forService(Service.IAS)
 				.withProperty(CFConstants.CLIENT_ID, "clientId")
@@ -100,10 +100,9 @@ public class SAPOfflineTokenServicesCloudTest {
 		cut = new SAPOfflineTokenServicesCloud(configuration, jwtValidatorBuilderSpy);
 
 		cut.afterPropertiesSet();
-		OAuth2Authentication authentication = cut.loadAuthentication(iasToken);
-
-		assertThat(authentication.isAuthenticated()).isTrue();
-		assertThat(authentication.getUserAuthentication()).isNull();
+		assertThatThrownBy(() -> cut.loadAuthentication(iasToken))
+				.isInstanceOf(InvalidTokenException.class)
+				.hasMessageContaining("AccessToken of service IAS is not supported");
 	}
 
 	@Test
diff --git a/java-security/src/test/java/com/sap/cloud/security/adapter/spring/SpringSecurityContextTest.java b/java-security/src/test/java/com/sap/cloud/security/adapter/spring/SpringSecurityContextTest.java
index 441a84eb..0385b93c 100644
--- a/java-security/src/test/java/com/sap/cloud/security/adapter/spring/SpringSecurityContextTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/adapter/spring/SpringSecurityContextTest.java
@@ -1,12 +1,9 @@
 package com.sap.cloud.security.adapter.spring;
 
-import com.sap.cloud.security.token.AccessToken;
-import com.sap.cloud.security.token.SapIdToken;
-import com.sap.cloud.security.token.Token;
-import com.sap.cloud.security.token.XsuaaToken;
+import com.sap.cloud.security.config.Service;
+import com.sap.cloud.security.token.*;
 import org.apache.commons.io.IOUtils;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContextHolder;
@@ -24,6 +21,8 @@ import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 
+import static com.sap.cloud.security.config.Service.IAS;
+import static com.sap.cloud.security.token.TokenClaims.SUBJECT;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
@@ -55,6 +54,9 @@ public class SpringSecurityContextTest {
 	public void getToken() {
 		setToken(sapIdToken, NO_SCOPES);
 		assertThat(SpringSecurityContext.getToken()).isEqualTo(sapIdToken);
+
+		setToken(token, NO_SCOPES);
+		assertThat(SpringSecurityContext.getToken()).isEqualTo(token);
 	}
 
 	@Test
@@ -76,12 +78,18 @@ public class SpringSecurityContextTest {
 	}
 
 	@Test
-	@Ignore // TODO IAS Support
-	public void getAccessTokenReturnsNullIfTokenDoesNotImplementInterface() {
+	public void getAccessTokenReturnsNull_inCaseOfIasToken() {
 		setToken(sapIdToken, NO_SCOPES);
 		assertThat(SpringSecurityContext.getAccessToken()).isNull();
 	}
 
+	@Test
+	public void getTokenReturnsIasOidcToken() {
+		setToken(sapIdToken, NO_SCOPES);
+		assertThat(SpringSecurityContext.getToken().getService()).isEqualTo(IAS);
+		assertThat(SpringSecurityContext.getToken().getClaimAsString(SUBJECT)).isEqualTo("P176945");
+	}
+
 	@Test
 	public void clear_removesToken() {
 		setToken(token, NO_SCOPES);
@@ -116,8 +124,8 @@ public class SpringSecurityContextTest {
 
 		HttpServletRequest request = mock(HttpServletRequest.class);
 		when(request.getAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE)).thenReturn(token.getTokenValue());
-
 		authentication.setDetails(new OAuth2AuthenticationDetails(request));
+
 		context.setAuthentication(authentication);
 		SecurityContextHolder.clearContext();
 		SecurityContextHolder.setContext(context);
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 917fe88a..21ea7f58 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
@@ -120,8 +120,8 @@ public class AbstractTokenTest {
 
 	@Test
 	public void getJsonObject() {
-		JsonObject externalAttributes = cut.getClaimAsJsonObject("ext_attr");
-		assertThat(externalAttributes.getAsString("enhancer")).isEqualTo("XSUAA");
+		JsonObject externalAttributes = cut.getClaimAsJsonObject(TokenClaims.XSUAA.EXTERNAL_ATTRIBUTE);
+		assertThat(externalAttributes.getAsString(TokenClaims.XSUAA.EXTERNAL_ATTRIBUTE_ENHANCER)).isEqualTo("XSUAA");
 	}
 
 	@Test
@@ -150,6 +150,11 @@ public class AbstractTokenTest {
 				.contains(cut.getClaimAsStringList(TokenClaims.XSUAA.SCOPES));
 	}
 
+	@Test
+	public void isXsuaaToken() {
+		assertThat(((AbstractToken) cut).isXsuaaToken()).isTrue();
+	}
+
 	@ParameterizedTest
 	@ValueSource(strings = { "cid", "", "    " })
 	public void getClientIdWithCidTest(String cid) throws InvalidTokenException {
diff --git a/java-security/src/test/resources/vcapIasServiceSingleBinding.json b/java-security/src/test/resources/vcapIasServiceSingleBinding.json
index 04bd18cb..848a9d8d 100644
--- a/java-security/src/test/resources/vcapIasServiceSingleBinding.json
+++ b/java-security/src/test/resources/vcapIasServiceSingleBinding.json
@@ -1,5 +1,5 @@
 {
-  "identity-beta": [
+  "identity": [
     {
       "binding_name": null,
       "credentials": {
@@ -8,9 +8,9 @@
         "url": "https://myauth.com"
       },
       "instance_name": "myservice",
-      "label": "identity-beta",
+      "label": "identity",
       "name": "myservice",
-      "plan": "default",
+      "plan": "application",
       "provider": null,
       "syslog_drain_url": null,
       "tags": [],
diff --git a/samples/java-security-usage-ias/src/test/java/com/sap/cloud/security/samples/ias/HelloJavaServletIntegrationTest.java b/samples/java-security-usage-ias/src/test/java/com/sap/cloud/security/samples/ias/HelloJavaServletIntegrationTest.java
new file mode 100644
index 00000000..d90595ab
--- /dev/null
+++ b/samples/java-security-usage-ias/src/test/java/com/sap/cloud/security/samples/ias/HelloJavaServletIntegrationTest.java
@@ -0,0 +1,80 @@
+package com.sap.cloud.security.samples.ias;
+
+import com.sap.cloud.security.config.Service;
+import com.sap.cloud.security.test.SecurityTestRule;
+import com.sap.cloud.security.token.SecurityContext;
+import com.sap.cloud.security.token.Token;
+import com.sap.cloud.security.token.TokenClaims;
+import com.sap.cloud.security.xsuaa.http.HttpHeaders;
+import org.apache.commons.io.IOUtils;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.HttpClients;
+import org.junit.*;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import static com.sap.cloud.security.test.SecurityTestRule.*;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class HelloJavaServletIntegrationTest {
+
+	@ClassRule
+	public static SecurityTestRule rule = getInstance(Service.IAS)
+			.useApplicationServer()
+			.addApplicationServlet(HelloJavaServlet.class, HelloJavaServlet.ENDPOINT);
+
+	@After
+	public void tearDown() {
+		SecurityContext.clearToken();
+	}
+
+	@Test
+	public void requestWithoutAuthorizationHeader_unauthenticated() throws IOException {
+		HttpGet request = createGetRequest(null);
+		try (CloseableHttpResponse response = HttpClients.createDefault().execute(request)) {
+			assertThat(response.getStatusLine().getStatusCode()).isEqualTo(HttpStatus.SC_UNAUTHORIZED); // 401
+		}
+	}
+
+	@Test
+	public void requestWithEmptyAuthorizationHeader_unauthenticated() throws IOException {
+		HttpGet request = createGetRequest("");
+		try (CloseableHttpResponse response = HttpClients.createDefault().execute(request)) {
+			assertThat(response.getStatusLine().getStatusCode()).isEqualTo(HttpStatus.SC_UNAUTHORIZED); // 401
+		}
+	}
+
+	@Test
+	public void request_withValidToken() throws IOException {
+		Token token = rule.getPreconfiguredJwtGenerator()
+				.withClaimValue(TokenClaims.EMAIL, "john.doe@email.com")
+				.createToken();
+		HttpGet request = createGetRequest(token.getTokenValue());
+		try (CloseableHttpResponse response = HttpClients.createDefault().execute(request)) {
+			String responseBody = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
+			assertThat(response.getStatusLine().getStatusCode()).isEqualTo(HttpStatus.SC_OK);
+			assertThat(responseBody).isEqualTo("You ('john.doe@email.com') are authenticated and can access the application.");
+		}
+	}
+
+	@Test
+	public void request_withInvalidToken_unauthenticated() throws IOException {
+		HttpGet request = createGetRequest(rule.getPreconfiguredJwtGenerator()
+				.withClaimValue(TokenClaims.ISSUER, "INVALID Issuer")
+				.createToken().getTokenValue());
+		try (CloseableHttpResponse response = HttpClients.createDefault().execute(request)) {
+			assertThat(response.getStatusLine().getStatusCode()).isEqualTo(HttpStatus.SC_UNAUTHORIZED); // 401
+		}
+	}
+
+	private HttpGet createGetRequest(String bearerToken) {
+		HttpGet httpGet = new HttpGet(rule.getApplicationServerUri() + HelloJavaServlet.ENDPOINT);
+		if(bearerToken != null) {
+			httpGet.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + bearerToken);
+		}
+		return httpGet;
+	}
+}
\ No newline at end of file