This migration guide is a step-by-step guide explaining how to replace the following SAP-internal Java Container Security Client libraries
- com.sap.xs2.security:java-container-security
- com.sap.cloud.security.xsuaa:java-container-security
with this open-source version.
Please note, that as of now, this Migration Guide is NOT intended for applications using SAP Java Buildpack.
You're using the SAP Java Buildpack, if you can find the sap_java_buildpack in the deployment descriptor of your application, e.g. in your manifest.yml file.
This documentation describes the setup when using SAP Java Buildpack.
The Spring Security OAuth project is deprecated. The latest OAuth 2.0 support is provided by Spring Security. See the OAuth 2.0 Migration Guide for further details.
The java-container-security as well as the SAPOfflineTokenServicesCloud provided as part of the current solution bases on org.springframework.security.oauth:spring-security-oauth2 which is deprecated. In case of Spring-Boot application you may want to follow this Migration Guide.
To use the new java-security client library the dependencies declared in maven pom.xml need to be updated.
First make sure you have the following dependencies defined in your pom.xml:
<!-- take the latest spring-security dependencies -->
<!-- Spring deprecates it as it gets replaced with libs of groupId "org.springframework.security" -->
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.4.1.RELEASE</version> <!-- chose the latest from maven repository -->
</dependency>
<!-- new java-security dependencies -->
<dependency>
<groupId>com.sap.cloud.security.xsuaa</groupId>
<artifactId>api</artifactId>
<version>2.13.7</version>
</dependency>
<dependency>
<groupId>com.sap.cloud.security</groupId>
<artifactId>java-security</artifactId>
<version>2.13.7</version>
</dependency>
<!-- new java-security dependencies for unit tests -->
<dependency>
<groupId>com.sap.cloud.security</groupId>
<artifactId>java-security-test</artifactId>
<version>2.13.7</version>
<scope>test</scope>
</dependency>Now you are ready to remove the java-container-security client library by deleting the following dependencies from the pom.xml:
| groupId (deprecated) | artifactId (deprecated) |
|---|---|
| com.sap.xs2.security | java-container-security |
| com.sap.xs2.security | api |
| com.sap.cloud.security.xssec | api |
| com.sap.cloud.security.xsuaa | java-container-security-api |
| com.sap.cloud.security.xsuaa | java-container-security |
Make sure that you do not refer to any other sap security library with group-id com.sap.security or com.sap.security.nw.sso.*.
After the dependencies have been changed, the spring security configuration needs some adjustments as well.
If your security configuration was using the SAPOfflineTokenServicesCloud class from the java-container-security library,
you need to change it slightly to use the SAPOfflineTokenServicesCloud adapter class from the new library.
Note: There is no replacement for
SAPPropertyPlaceholderConfigureras you can always parameterize theSAPOfflineTokenServicesCloudbean with yourOAuth2ServiceConfiguration.
For example see the following snippet on how to instantiate the SAPOfflineTokenServicesCloud.
@Bean
@Profile("cloud")
protected SAPOfflineTokenServicesCloud offlineTokenServices() {
return new SAPOfflineTokenServicesCloud(
Environments.getCurrent().getXsuaaConfiguration(), //optional
new RestTemplate()) //optional
.setLocalScopeAsAuthorities(true); //optional
}You might need to fix your java imports to get rid of the old import for the SAPOfflineTokenServicesCloud class.
As you may have updated the
In case of XML-based Spring (Security) configuration you need to replace your current SAPOfflineTokenServicesCloud bean definition with that:
<bean id="offlineTokenServices"
class="com.sap.cloud.security.adapter.spring.SAPOfflineTokenServicesCloud">
<property name="localScopeAsAuthorities" value="true" />
</bean>With localScopeAsAuthorities you can perform spring scope checks without referring to the XS application name (application id), e.g. myapp!t1234. For example:
Or
...
.authorizeRequests()
.antMatchers(POST, "/api/v1/ads/**").access("#oauth2.hasScopeMatching('Update')") //instead of '${xs.appname}.Update'<sec:intercept-url pattern="/rest/addressbook/deletedata" access="#oauth2.hasScope('Delete')" method="GET" />There is no need to configure SAP_JWT_TRUST_ACL within your deployment descriptor such as manifest.yml.
Instead the Xsuaa service instance adds audiences to the issued JSON Web Token (JWT) as part of the aud claim.
Whether the token is issued for your application or not is now validated by the JwtAudienceValidator.
This comes with a change regarding scopes. For a business application A that wants to call an application B, it's now mandatory that the application B grants at least one scope to the calling business application A. You can grant scopes with the xs-security.json file. For additional information, refer to the Application Security Descriptor Configuration Syntax, specifically the sections referencing the application and authorities.
You may have code parts that requests information from the access token, like the user's name, its tenant, and so on. So, look up your code to find its usage.
import com.sap.xs2.security.container.SecurityContext;
import com.sap.xs2.security.container.UserInfo;
import com.sap.xs2.security.container.UserInfoException;
try {
UserInfo userInfo = SecurityContext.getUserInfo();
String logonName = userInfo.getLogonName();
} catch (UserInfoException e) {
// handle exception
}This can be easily replaced with the Token or XsuaaToken Api.
import com.sap.cloud.security.token.*;
AccessToken token = SecurityContext.getAccessToken();
String logonName = token.getClaimAsString(TokenClaims.USER_NAME);
boolean hasDisplayScope = token.hasLocalScope("Display");
GrantType grantType = token.getGrantType();Note, that no
XSUserInfoExceptionis raised, in case the token does not contain the requested claim.
When you're done with the first part and need further information from the token you can use XSUserInfoAdapter in order to access the remaining methods exposed by the XSUserInfo Interface.
try {
XSUserInfo userInfo = new XSUserInfoAdapter(token);
String dbToken = userInfo.getHdbToken();
} catch (XSUserInfoException e) {
// handle exception
}If you want to overwrite the service configuration of the SAPOfflineTokenServicesCloud for your test, you can do so by
using some test constants provided by the test library. The following snippet shows how to do that:
import static com.sap.cloud.security.config.cf.CFConstants.*;
@Configuration
public class TestSecurityConfig {
@Bean
@Primary
public SAPOfflineTokenServicesCloud sapOfflineTokenServices() {
OAuth2ServiceConfiguration configuration = OAuth2ServiceConfigurationBuilder
.forService(Service.XSUAA)
.withClientId(SecurityTestRule.DEFAULT_CLIENT_ID)
.withProperty(CFConstants.XSUAA.APP_ID, SecurityTestRule.DEFAULT_APP_ID)
.withProperty(CFConstants.XSUAA.UAA_DOMAIN, SecurityTestRule.DEFAULT_DOMAIN)
.build();
return new SAPOfflineTokenServicesCloud(configuration).setLocalScopeAsAuthorities(true);
}
}In your unit test you might want to generate jwt tokens and have them validated. The java-security-test library provides it's own JwtGenerator. This can be embedded using the
SecurityTestRule. See the following snippet as example:
@ClassRule
public static SecurityTestRule securityTestRule =
SecurityTestRule.getInstance(Service.XSUAA)
.setKeys("/publicKey.txt", "/privateKey.txt");Using the SecurityTestRule you can use a preconfigured JwtGenerator to create JWT tokens with custom scopes for your tests. It configures the JwtGenerator in such a way that it uses the public key from the publicKey.txt file to sign the token.
static final String XSAPPNAME = SecurityTestRule.DEFAULT_APP_ID;
static final String DISPLAY_SCOPE = XSAPPNAME + ".Display";
static final String UPDATE_SCOPE = XSAPPNAME + ".Update";
String jwt = securityTestRule.getPreconfiguredJwtGenerator()
.withScopes(DISPLAY_SCOPE, UPDATE_SCOPE)
.createToken()
.getTokenValue();Make sure, that your JUnit tests are running.
The java-security-test library provides also JUnit 5 support as described here.
For local testing you might need to provide custom VCAP_SERVICES before you run the application.
The new security library requires the following key value pairs in the VCAP_SERVICES
under xsuaa/credentials for jwt validation:
"uaadomain" : "localhost""verificationkey" : "<public key your jwt token is signed with>"
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 yourJUnittests to fetch the value ofjwtfrom there.
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.
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.
If you get errors in the aplication log similar to this one
Configuration problem: You cannot use a spring-security-4.0.xsd or spring-security-4.1.xsd schema with Spring Security 4.2. Please update your schema declarations to the 4.2 schema. Offending resource: ServletContext resource [/WEB-INF/spring-security.xml]
You can fix this by removing the Spring versions of the schema declaration in the file /WEB-INF/spring-security.xml. Without explicit versions set, the latest version will be used.
xsi:schemaLocation="http://www.springframework.org/schema/security/oauth2
http://www.springframework.org/schema/security/spring-security-oauth2.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
If you get an org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException like this:
org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException: Line XX in XML document from ServletContext resource [/WEB-INF/spring-security.xml] is invalid;
nested exception is org.xml.sax.SAXParseException; lineNumber: 51; columnNumber: 118; cvc-complex-type.2.4.c:
The matching wildcard is strict, but no declaration can be found for element 'oauth:resource-server'.
You can fix this by changing the schema location to https for oauth2 as below in the /WEB-INF/spring-security.xml. With this change, the local jar is available and solves the issue of server trying to connect to get the jar and fails due to some restrictions.
xsi:schemaLocation="http://www.springframework.org/schema/security/oauth2
https://www.springframework.org/schema/security/spring-security-oauth2.xsd
If you use SAPOfflineTokenServicesCloud together with HttpServlet scope checks (e.g. httpRequest.isUserInRole), you might get 403 unauthorized errors even though the token contains the correct scope. This can happen if automatic ROLE_prefixing is enabled in Spring and the roles of the token do not have a ROLE_ prefix. To fix this problem you can disable the automatic ROLE_prefixing in Spring. This is described here. Anther way to fix this problem is to manually prefix all scopes with ROLE_. After that your scope checks should work as expected.
In case you face issues to apply the migration steps check this troubleshoot for known issues and how to file the issue.