Token Validation for Spring Boot applications. It integrates java-security to Spring Security Framework to support validations for tokens issued by these SAP Business Technology Platform identity services: xsuaa and identity.
It fully integrates with Spring Security OAuth 2.0 Resource Server.
- The credentials from the identity services can be configured as configuration properties.
- Decodes and parses encoded JSON Web Tokens (
Token) and provides convenient access to token header parameters and claims. - Validates the decoded token using
java-securitysecurity client library.
- Cloud Foundry
- Kubernetes/Kyma
- XSUAA
- IAS tokens from multiple tenants and zones
| JWS | Algorithm | Description |
|---|---|---|
| RS256 | RSA256 | RSASSA-PKCS1-v1_5 with SHA-256 |
Library supports services provisioned by SAP BTP service-operator To access service instance configurations from the application, Kubernetes secrets need to be provided as files in a volume mounted on application's container.
- BTP Service-operator up to v0.2.2 - Library will look up the configuration files in the following paths:
- XSUAA:
/etc/secrets/sapbtp/xsuaa/<YOUR XSUAA INSTANCE NAME> - IAS:
/etc/secrets/sapbtp/identity/<YOUR IAS INSTANCE NAME>
- XSUAA:
- BTP Service-operator starting from v0.2.3 - Library reads the configuration from k8s secret that is stored in a volume, this volume's
mountPathmust be defined in environment variableSERVICE_BINDING_ROOT.- upon creation of service binding a kubernetes secret with the same name as the binding is created. This binding secret needs to be stored to pod's volume.
SERVICE_BINDING_ROOTenvironment variable needs to be defined with value that points to volume mount's directory (mounthPath) where service binding secret will be stored. e.g. like here
Detailed information on how to use spring-security library in Kubernetes/Kyma environment can be found in spring-security-hybrid-usage sample README.
These (spring) dependencies needs to be provided:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.sap.cloud.security</groupId>
<artifactId>resourceserver-security-spring-boot-starter</artifactId>
<version>2.13.7</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>As auto-configuration requires Spring Boot specific dependencies, it is enabled when using xsuaa-spring-boot-starter Spring Boot Starter.
Then it auto-configures beans, that are required to initialize the Spring Boot application as OAuth resource server.
| Auto-configuration class | Description |
|---|---|
| HybridAuthorizationAutoConfiguration | Creates a converter (XsuaaTokenAuthorizationConverter) that removes the xsuaa application identifier from the scope names to enable local scope checks using Spring's common built-in expression hasAuthority. |
| HybridIdentityServicesAutoConfiguration | Configures a JwtDecoder which is able to decode and validate tokens from Xsuaa and Identity service or Identity service alone. Furthermore it registers the IdentityServiceConfiguration and optionally XsuaaServiceConfiguration, that gets configured with identity.* and xsuaa.* properties. |
| XsuaaTokenFlowAutoConfiguration | Configures a XsuaaTokenFlows bean to fetch the XSUAA service binding information. Starting with 2.10.0 version it supports X.509 based authentication. |
| SecurityContextAutoConfiguration | Configures JavaSecurityContextHolderStrategy class as SecurityContextHolderStrategy keeps the com.sap.cloud.security.token.SecurityContext in sync. |
| Auto-configuration property | Default value | Description |
|---|---|---|
| sap.spring.security.hybrid.auto | true | This enables all auto-configurations that setup your project for hybrid IAS and XSUAA token validation. |
| sap.spring.security.xsuaa.flows.auto | true | This enables all auto-configurations required for xsuaa token exchange using token-client library. |
You can gradually replace auto-configurations as explained here.
Configure your application as Spring Security OAuth 2.0 Resource Server for authentication of HTTP requests:
@Configuration
@EnableWebSecurity
@PropertySource(factory = IdentityServicesPropertySourceFactory.class, ignoreResourceNotFound = true, value = { "" }) // might be auto-configured in a future release
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
Converter<Jwt, AbstractAuthenticationToken> authConverter; // required in case of xsuaa
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/sayHello").hasAuthority("Read")
.antMatchers("/*").authenticated()
.anyRequest().denyAll()
.and()
.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(authConverter); // (1) you may want to provide your own converter
// @formatter:on
}
}💡 This
PropertySourcemight be auto-configured soon. Please watch the release notes.
💡 Please note that the auto-configured authentication converter supportshasAuthority-checks for scopes provided with the Xsuaa access token. In case you need to consider authorizations provided via an OIDC token from IAS you need to overwrite the default implementation.
Create your own Authorization Converter by implementing Converter<Jwt, AbstractAuthenticationToken> interface.
In this sample it delegates to the autowired authConverter in case of an Xsuaa access token.
class MyCustomTokenAuthConverter implements Converter<Jwt, AbstractAuthenticationToken> {
public AbstractAuthenticationToken convert(Jwt jwt) {
if(jwt.containsClaim(TokenClaims.XSUAA.EXTERNAL_ATTRIBUTE)) { // required in case of xsuaa
return authConverter.convert(jwt); // @Autowired Converter<Jwt, AbstractAuthenticationToken> authConverter;
}
return new AuthenticationToken(jwt, deriveAuthoritiesFromGroup(jwt));
}
private Collection<GrantedAuthority> deriveAuthoritiesFromGroup(Jwt jwt) {
Collection<GrantedAuthority> groupAuthorities = new ArrayList<>();
if (jwt.containsClaim(TokenClaims.GROUPS)) {
List<String> groups = jwt.getClaimAsStringList(TokenClaims.GROUPS);
for (String group: groups) {
groupAuthorities.add(new SimpleGrantedAuthority(group.replace("IASAUTHZ_", "")));
}
}
return groupAuthorities;
}
}... finally configure Spring's resource server with an instance of this custom converter.
In the Java coding, use the com.sap.cloud.security.token.Token to extract user information from the token:
@GetMapping("/getGivenName")
public String getGivenName(@AuthenticationPrincipal Token token) {
return token.getClaimAsString(TokenClaims.GIVEN_NAME)
}💡 Make sure you've imported the right Token:
com.sap.cloud.security.token.Token.
Spring Security supports authorization semantics at the method level. As prerequisite you need to enable global Method Security as explained in Baeldung tutorial: Introduction to Spring Method Security.
@GetMapping("/hello-token")
@PreAuthorize("hasAuthority('Read')")
public Map<String, String> message() {
...
}In case you need information from VCAP_SERVICES system environment variable from one of the identity services, you have these options:
... in case you are bound to a single xsuaa service instance:
@Autowired
XsuaaServiceConfiguration xsuaaServiceConfiguration; ... in case you are bound to multiple xsuaa service instances
@Autowired
XsuaaServiceConfigurations xsuaaServiceConfigurations;... in case you are bound to an identity service instance
@Autowired
IdentityServiceConfiguration identityServiceConfiguration;Alternatively, you can also access the information with Environments.getCurrent(), which is provided with java-security.
In case you have implemented a central Exception Handler as described with Baeldung Tutorial: Error Handling for REST with Spring you may want to emit logs to the audit log service in case of AccessDeniedExceptions.
Alternatively there are also various options provided with Spring.io. For example, you can integrate SAP audit log service with Spring Boot Actuator audit framework as described here.
In case of non-HTTP requests, you may need to initialize the Spring Security Context with a JWT token you've received from a message, an event or you've requested from the identity service directly:
import org.springframework.security.oauth2.jwt.JwtDecoder;
public class Listener {
@Autowired
JwtDecoder jwtDecoder;
@Autowired
Converter<Jwt, AbstractAuthenticationToken> authConverter;
public void onEvent(String encodedToken) {
if (encodedToken != null) {
SpringSecurityContext.init(encodedToken, jwtDecoder, authConverter);
}
try {
handleEvent();
} finally {
SpringSecurityContext.clear();
}
}
}In detail com.sap.cloud.security.token.SpringSecurityContext wraps the Spring Security Context (namely SecurityContextHolder.getContext()), which stores by default the information in ThreadLocals. In order to avoid memory leaks it is recommended to remove the current thread's value for garbage collection.
💡 Note that
SpringSecurityContextis thread-bound and is NOT propagated to child-threads. This Baeldung tutorial: Spring Security Context Propagation article provides more information on how to propagate the context.
- java-security-test offers test utilities to generate custom JWT tokens for the purpose of tests. It pre-configures a WireMock web server to stub outgoing calls to the identity service (OAuth resource-server), e.g. to provide token keys for offline token validation. Its use is intended for JUnit tests only.
In case of local testing, there might be no VCAP_SERVICES system environment variable, or in case of JUnit testing the values may have to be overwritten. In these cases, you can set or overwrite the default property sources.
Go to application.yml and provide for example the following configuration:
sap.security.services:
identity:
clientid: sb-clientId!t0815 # SecurityTest.DEFAULT_CLIENT_ID
domains:
- localhost # SecurityTest.DEFAULT_DOMAIN
xsuaa:
xsappname: xsapp!t0815 # SecurityTest.DEFAULT_APP_ID
uaadomain: localhost # SecurityTest.DEFAULT_DOMAIN
clientid: sb-clientId!t0815 # SecurityTest.DEFAULT_CLIENT_ID
url: http://localhost # SecurityTest.DEFAULT_URL
# clientsecret: required for token-flows apiIf your application binds to two XSUAA service instances (e.g. one of plan application and another one of plan broker), you may want to map the properties to VCAP_SERVICES for local testing. Go to application.yml and apply the following two changes:
- provide instead of
sap.security.services.xsuaasap.security.services.xsuaa[0]and - provide additional the client-ids of the other xsuaa service(s)
Finally, it should look as following:
sap.security.services:
xsuaa[0]:
... # credentials of xsuaa of plan 'application'
xsuaa[1]:
clientid: # clientid of xsuaa of plan 'broker' In case you face issues, file an issue on Github and provide these details:
- security related dependencies, get maven dependency tree with
mvn dependency:tree - debug logs
- issue you’re facing.
First, configure the Debug log level for Spring Framework Web and all Security related libs. This can be done as part of your application.yml or application.properties file.
logging.level:
com.sap.cloud.security: DEBUG # set SAP-class loggers to DEBUG; set to ERROR for production setup
org.springframework: ERROR # set to DEBUG to see all beans loaded and auto-config conditions met
org.springframework.security: DEBUG # set to ERROR for production setup
org.springframework.web: DEBUG # set to ERROR for production setupThen, in case you like to see what different filters are applied to particular request then set debug flag to true in @EnableWebSecurity annotation:
@Configuration
@EnableWebSecurity(debug = true) // TODO "debug" may include sensitive information. Do not use in a production system!
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
...
}Finally, you need do re-deploy your application for the changes to take effect.
We recognized that this error is raised, when your instance name contains upper cases.
When you're trying to run the application locally, but application fails to start with the following error message:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.core.convert.converter.Converter<org.springframework.security.oauth2.jwt.Jwt, org.springframework.security.authentication.AbstractAuthenticationToken>' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
***************************
APPLICATION FAILED TO START
***************************
Field authConverter in com.sap.cloud.test.SecurityConfiguration required a bean of type 'org.springframework.core.convert.converter.Converter' that could not be found.
Make sure that you have defined the following mandatory attribute in the service configuration (VCAP_SERVICES env variable or application.yaml or application.properties)
- xsappname
💡 Example of minimal application configuration application.yml for local setup.
- Sample
demonstrating how to leveragespring-securitylibrary to secure a Spring Boot web application with tokens issued by xsuaa or identity service. Furthermore it documents how to implement SpringWebMvcTests usingjava-security-testlibrary.