Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
During development of our Cloud Foundry Java app we came to a point where we wanted to test the security in our endpoint. We want to check if endpoints are secured and if you can call them with the needed rights.

 

Our Service Setup


 

  • Java 8 application

  • Build tool: Maven

  • Spring Boot 2.x.x

  • Java security 2.x.x

    • Dependency: com.sap.cloud.security.xsuaa:spring-xsuaa



  • Cloud Application Programming Model (CAP) 1.x.x using OData version 2

    • Dependency: com.sap.cloud.servicesdk.prov:odatav2-spring-boot-starter



  • Cloud Application Programming CDS data store

  • SAP Cloud SDK 2.x.x

    • Dependency: com.sap.cloud.s4hana.cloudplatform



  • SAP Enterprise Messaging


This blog post concentrate on the security part (Java security 2.x.x: com.sap.cloud.security.xsuaa:spring-xsuaa) in Spring Boot tests which are called integration tests in the following sections.

What is not shown here is how to configure Spring Boot security in a project. This blog post only concentrate on the testing part after the configuration was done.

 

The testing of the security in our apps has also one additional aspect. We want to test is if the correct tenant in used from the JWT which comes with the header data of a HTTP request.

 

To test both aspects of the security topic we need two things.

  1. The possibility to generated JWT with a given scope and tenant

  2. The possibility to provide a mock for the XSUAA service which is bound to the Java during runtime in a space in Cloud Foundry



Test setup


 

For generating JWT during our tests we need to add the following dependency to our service. The dependency has the scope "test" which means it is only available for tests but not for productive usage:
<dependency>
<groupId>com.sap.cloud.security.xsuaa</groupId>
<artifactId>spring-xsuaa-test</artifactId>
<version>1.5.0</version>
<scope>test</scope>
</dependency>

In addition, the following post processor needs to be implemented to set a mock server for XSUAA:
public class TestingEnvironmentPostProcessor implements EnvironmentPostProcessor, DisposableBean {

public final static String UNIT_TEST_PROFILE = "unit_testing";

private final XsuaaMockWebServer mockAuthorizationServer = new XsuaaMockWebServer();

@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
if (environment.acceptsProfiles(Profiles.of(UNIT_TEST_PROFILE))) {
// sets a mock-server for xsuaa
environment.getPropertySources().addFirst(this.mockAuthorizationServer);
}
}

@Override
public void destroy() throws Exception {
this.mockAuthorizationServer.destroy();
}
}

With this mock server we mock the XSUAA service but only if a spring profile "unit_testing" is used.

But only implementing the mock post processor is not enough to tell Spring that the post processor shall be used. To enable the post processor we have to add the following line in the spring.factories file. This file has the name "spring.factories" and need to be placed in a folder "META-INF" in the tests resources folder.

File name:
src/test/resources/META-INF/spring.factories

example content of the file:
#org.springframework.boot.env.EnvironmentPostProcessor=<yourPackage>.<yourEnvironmentPostProcessor>
org.springframework.boot.env.EnvironmentPostProcessor=com.sap.testapp.security.TestingEnvironmentPostProcessor

 

To enable the profile which uses the post processor in the integration tests we have to add the following annotation to the tests:
@ActiveProfiles(profiles = TestingEnvironmentPostProcessor.UNIT_TEST_PROFILE)

 

After the post processor is set up one last step needs to be done to get the mock server working. The app name which shall be used as name of the app that is running needs to be specified in the application property file like we did it in our case in an application.yaml file in the resources folder of the tests:
xsuaa:
xsappname: testapp-srv

This app name will be used from the XSUAA mock server. So, if also scope checks needs to be tested this app name needs to be included in the scope the is used in JWT during the tests.

Scope in Spring always have two parts:

  1. The app name

  2. The Scope name


So if you want to have a Scope "Write" in an app "testapp" the scope which needs to be added in the JWT is "testapp.Write".


Integration tests


 

Jwt generator


 

Now we have setup all the needed thinks and now we want to test the security of our endpoints and if the correct tenant is used.

In our case and example below, we use the MockMvc to call our endpoints. But the same technic can also be used if other tools like e.g. TestRestTemplate are used.

To setup a test using the mock server the profile mentioned above must be used. So, the test class could look like:
@RunWith(SpringRunner.class)
@SpringBootTest
@ActiveProfiles(profiles = TestingEnvironmentPostProcessor.UNIT_TEST_PROFILE)
public class SomeTestClassIT

Now the JWT needs to be generated. For this a JWT generator is used. Using the JWT generator also properties of the JWT like scope, tenant or others can be set:
    private static final String MY_CLIENT_ID = "sb-clientId!20";
private static final String MY_SUBDOMAIN = "my-subaccount-subdomain";
private static final String MY_TENANT= "my tenant";

private JwtGenerator jwtGenerator;

@Autowired
private XsuaaServiceConfiguration xsuaaServiceConfiguration;

@Before
public void beforeEachTest() {
jwtGenerator = new JwtGenerator(MY_CLIENT_ID, MY_SUBDOMAIN, MY_TENANT);
jwtGenerator.addScopes(getGlobalScope("Test"));
}

private String getGlobalScope(String localScope) {
String appId = xsuaaServiceConfiguration.getAppId();
return appId + "." + localScope;
}

In this example coding a jwtGenerator is created with some properties like client id, subdomain or tenant.

After the jwtGenerator is create a scope is added.

Like described above the scope id a combination of app name and scope name. To get the app name a configuration class is auto wired. This configuration class is also used by the mock server. From this configuration the app name (getAppId()) is retrieved and combined with the local defined scope.

If tests shall be done that e.g. if other app names are used the endpoints can't be called also other scopes can be added to the jwtGenerator.

 

Setup MockMvc


 

After the JWT generator is setup the mock MVC needs to be setup.

This is done with the following coding:
    private MockMvc mvc;

@Before
public void beforeEachTest() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(SecurityMockMvcConfigurers.springSecurity())
.build();
}

Important here is the line:
.apply(SecurityMockMvcConfigurers.springSecurity())

This enables the security in the MockMvc. Without this line the endpoint can be called but security is not checked.

Because of this it is very important to write also tests that have wrong scopes or wrong JWT to see, that such requests are response with unauthorized or something equal.

 

Using JWTs


 

To use the jwtGenerator now in tests you can call the following method on the jwtGenerator:
String jwt = jwtGenerator.getTokenForAuthorizationHeader();

Now, this can be used to send requests:
MvcResult result = mvc.perform(post("testEndpoint")
.header(HttpHeaders.AUTHORIZATION, jwt)
.contentType(MediaType.APPLICATION_JSON)
.content("some content"))
.andReturn();

 

Using for Security Context


 

In some tests we will not test a web service call but only some classes working together. But also, here it could be needed, that the security context is set. Also, for such a case the JWT generator can be used.

Here the JWT will be set to the security context before the test:
    @Before
public void setup(){
jwtGenerator = new JwtGenerator(MY_CLIENT_ID, MY_SUBDOMAIN);
jwtGenerator.addScopes(getGlobalScope("Test"));

SpringSecurityContext.init(jwtGenerator.getToken().getTokenValue(), jwtDecoder,
new LocalAuthoritiesExtractor(xsuaaServiceConfiguration.getAppId()));
}

 

Example for verify security in HTTP endpoint


 

An complete example for calling a HTTP endpoint could look like:
package com.sap.testapp.integrationtests.webcall;

import com.sap.cloud.security.xsuaa.XsuaaServiceConfiguration;
import com.sap.cloud.security.xsuaa.test.JwtGenerator;
import com.sap.testapp.testapplication.configuration.TestingEnvironmentPostProcessor;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;

@RunWith(SpringRunner.class)
@SpringBootTest
@ActiveProfiles(profiles = TestingEnvironmentPostProcessor.UNIT_TEST_PROFILE)
public class SpringIntegrationTestIT {

private static final String MY_CLIENT_ID = "sb-clientId!20";
private static final String MY_SUBDOMAIN = "my-subaccount-subdomain";
private static final String MY_TENANT = "my tenant";

@Autowired
protected WebApplicationContext context;
@Autowired
private XsuaaServiceConfiguration xsuaaServiceConfiguration;

private JwtGenerator jwtGenerator;
private MockMvc mvc;

@Before
public void beforeEachTest() {
jwtGenerator = new JwtGenerator(MY_CLIENT_ID, MY_SUBDOMAIN, MY_TENANT);
jwtGenerator.addScopes(getGlobalScope("Test"));

mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(SecurityMockMvcConfigurers.springSecurity())
.build();
}

@Test
public void endpointCalled() throws Exception {
String jwt = jwtGenerator.getTokenForAuthorizationHeader();

MvcResult result = executeGetRequest(jwt);

assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.OK.value());
}

@Test
public void endpointCalledNotAuthorized() throws Exception {
String jwt = "some failing jwt";

MvcResult result = executeGetRequest(jwt);

assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.UNAUTHORIZED.value());
}

private String getGlobalScope(String localScope) {
String appId = xsuaaServiceConfiguration.getAppId();
return appId + "." + localScope;
}

private MvcResult executeGetRequest(String jwt) throws Exception {
return mvc.perform(get("/odata/test")
.header(HttpHeaders.AUTHORIZATION, jwt))
.andReturn();
}

}

 

Conclusion


 

In this blog post I showed what needs to be done to test Spring Boot security in Java applications. Some settings need to be done but after the configuration it is easy to test the security of HTTP endpoints.

Also mocking JWT including scope, tenants and other fields is possible.

 

Back to overview