This library enhances the spring-security project. As of version 5 of spring-security, this includes the OAuth resource-server functionality. A Spring boot application needs a security configuration class that enables the resource server and configures authentication using JWT tokens.
These (spring) dependencies needs to be provided:
<dependency> <!-- includes spring-security-oauth2 -->
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>com.sap.cloud.security.xsuaa</groupId>
<artifactId>spring-xsuaa</artifactId>
<version>2.8.4</version>
</dependency>
<dependency> <!-- new with version 1.5.0 -->
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
</dependency>
Or, if you like to leverage auto-configuration:
<dependency>
<groupId>com.sap.cloud.security.xsuaa</groupId>
<artifactId>xsuaa-spring-boot-starter</artifactId>
<version>2.8.4</version>
</dependency>
As auto-configuration requires Spring Boot specific dependencies, it is enabled when using xsuaa-spring-boot-starter
Spring Boot Starter.
Then, xsuaa integration libraries auto-configures beans, that are required to initialize the Spring Boot application as OAuth resource server.
Auto-configuration class | Description |
---|---|
XsuaaAutoConfiguration | Adds xsuaa.* properties to Spring's Environment. The properties are by default parsed from VCAP_SERVICES system environment variables and can be overwritten by properties such as xsuaa.xsappname e.g. for testing purposes. Furthermore it exposes a XsuaaServiceConfiguration bean that can be used to access xsuaa service information. Alternatively you can access them with @Value annotation e.g. @Value("${xsuaa.xsappname:}") String appId . |
XsuaaResourceServerJwkAutoConfiguration | Configures a JwtDecoder bean with a JWK (JSON Web Keys) endpoint from where to download the tenant (subdomain) specific public key. |
XsuaaTokenFlowAutoConfiguration | Configures a XsuaaTokenFlows bean for a given RestOperations and XsuaaServiceConfiguration bean to fetch the XSUAA service binding information. |
You can gradually replace auto-configurations as explained here.
Please note, in case your application exposes already one or more Spring beans of type RestOperations
(or its subclasses such as RestTemplate
), XsuaaAutoConfiguration
will not create a bean, but reuses the existing one.
In case there are multiple ones the auto-configurations do not know, which RestOperations
bean to select. In this case you can annotate the preferred RestOperations
bean with @Primary
.
In case you do not want to use the RestOperations
bean, that is specified in your Spring application context but still like to leverage the auto-configuration of spring-xsuaa
you can also provide a dedicated bean with name xsuaaRestOperations
:
@Configuration
public static class RestClientConfiguration {
@Bean
@LoadBalanced
public OAuth2RestTemplate myOAuth2RestTemplate() {
return new OAuth2RestTemplate(...);
}
@Bean
public RestTemplate xsuaaRestOperations() {
return new RestTemplate();
}
}
Configure the OAuth resource server
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
XsuaaServiceConfiguration xsuaaServiceConfiguration;
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/hello-token/**").hasAuthority("Read") // checks whether it has scope "<xsappId>.Read"
.antMatchers("/actuator/**").authenticated()
.anyRequest().denyAll()
.and()
.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(getJwtAuthoritiesConverter());
// @formatter:on
}
Converter<Jwt, AbstractAuthenticationToken> getJwtAuthoritiesConverter() {
TokenAuthenticationConverter converter = new TokenAuthenticationConverter(xsuaaServiceConfiguration);
converter.setLocalScopeAsAuthorities(true); // not applicable in case of multiple xsuaa bindings!
return converter;
}
}
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 / event or you've requested from XSUAA directly:
@Autowired
XsuaaServiceConfiguration xsuaaServiceConfiguration;
@Autowired
JwtDecoder jwtDecoder;
public void onEvent(String myEncodedJwtToken) {
if (myEncodedJwtToken != null) {
SpringSecurityContext.init(myEncodedJwtToken, jwtDecoder, new LocalAuthoritiesExtractor(xsuaaServiceConfiguration.getAppId()));
}
try {
handleEvent();
} finally {
SpringSecurityContext.clear();
}
}
In detail com.sap.cloud.security.xsuaa.token.SpringSecurityContext
wraps the Spring Security Context (namely SecurityContextHolder.getContext()
), which stores by default the information in ThreadLocal
s. In order to avoid memory leaks it is recommended to remove the current thread's value for garbage collection.
Note that Spring Security Context is 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.
In case application is required to do token exchange token-client
with all its' transitive dependencies need to be available(shouldn't be excluded) in the project.
<dependency>
<groupId>com.sap.cloud.security.xsuaa</groupId>
<artifactId>token-client</artifactId>
</dependency>
To enable token exchange between IAS and XSUAA system environment variable 'IAS_XSUAA_XCHANGE_ENABLED' needs to be provided and enabled. To enable the exchange set the value to any value except 'false' or empty. The exchange between IAS and Xsuaa is disabled by default.
To use the token exchange, by default the bearerTokenResolver
needs to be defined in the application's security configuration in the following manner:
http.authorizeRequests()
.antMatchers("/secured/path/**").hasAuthority("application.Read")
.anyRequest().denyAll()
.and().oauth2ResourceServer()
.bearerTokenResolver(new IasXsuaaExchangeBroker(xsuaaServiceConfiguration));
where xsuaaServiceConfiguration
represents configuration properties from environment.
Note: if you've already configured a
bearerTokenResolver
using TokenBrokerResolver that supports token exchange between IAS to Xsuaa enabled in the same way likeIasXsuaaExchangeBroker
.
Please check out also our samples:
- Spring-security-basic-auth sample for
TokenBrokerResolver
usage with basic authentication method. - Spring-security-xsuaa-usage sample for
IasXsuaaExchangeBroker
usage with OAuth2 authentication method
In the Java coding, use the Token
to extract user information:
@GetMapping("/getGivenName")
public String getGivenName(@AuthenticationPrincipal Token token) {
return token.getGivenName();
}
Or alternatively:
public String getGivenName() {
Token token = SpringSecurityContext.getToken();
return token.getGivenName();
}
Note: make sure that you've imported the right Token:
com.sap.cloud.security.xsuaa.token.Token
.
@GetMapping(@AuthenticationPrincipal Token token)
public ResponseEntity<YourDto> readAll() {
if (!token.getAuthorities().contains(new SimpleGrantedAuthority("Display"))) {
throw new NotAuthorizedException("This operation requires \"Display\" scope");
}
}
...
@ResponseStatus(HttpStatus.FORBIDDEN) //set status code to '403'
class NotAuthorizedException extends RuntimeException {
public NotAuthorizedException(String message) {
super(message);
}
}
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('Display')")
public Map<String, String> message() {
...
}
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 AccessDeniedException
s.
Alternativly 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 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
- (SAP) Java buildpack version, e.g. 1.26.1
- 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: DEBUG # set SAP-class loggers to DEBUG. Set to ERROR for production setups.
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 setups.
org.springframework.web: DEBUG # set to ERROR for production setups.
Then, in case you like to see what different filters are applied to particular request then set the 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.
java.lang.IllegalStateException: Failed to load ApplicationContext
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'springSecurityFilterChain' defined in class path resource [org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [javax.servlet.Filter]: Factory method 'springSecurityFilterChain' threw exception; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.security.oauth2.jwt.JwtDecoder' available
As of version 1.6.0
you need to make use of XSUAA Spring Boot Starter in order to leverage auto-configuration.
Make use of the Xsuaa Spring Boot Starter dependency as explained here.
Parameter 1 of method xsuaaJwtDecoder in com.sap.cloud.security.xsuaa.autoconfiguration.XsuaaResourceServerJwkAutoConfiguration required a single bean, but 2 were found...
In case you use the xsuaa-spring-boot-starter
, read the auto-configuration section.
If your application is bound to two XSUAA service instances (one of plan application
and another one of plan broker
), you run into the following issue:
Caused by: java.lang.RuntimeException: Found more than one xsuaa binding. There can only be one.
at com.sap.cloud.security.xsuaa.XsuaaServicesParser.getJSONObjectFromTag(XsuaaServicesParser.java:91)
at com.sap.cloud.security.xsuaa.XsuaaServicesParser.searchXSuaaBinding(XsuaaServicesParser.java:72)
at com.sap.cloud.security.xsuaa.XsuaaServicesParser.getAttribute(XsuaaServicesParser.java:59)
at com.sap.cloud.security.xsuaa.XsuaaServicePropertySourceFactory.getConfigurationProperties(XsuaaServicePropertySourceFactory.java:65)
at com.sap.cloud.security.xsuaa.XsuaaServicePropertySourceFactory.createPropertySource(XsuaaServicePropertySourceFactory.java:55)
at org.springframework.context.annotation.ConfigurationClassParser.processPropertySource(ConfigurationClassParser.java:452)
The library does not support more than one XSUAA binding. Follow these steps, to adapt your Spring Security Configuration.
We recognized that this error is raised, when your instance name contains upper cases.
Alternatively you can then define your XsuaaCredentials
Bean the following way:
@Bean
public XsuaaCredentials xsuaaCredentials() {
final XsuaaCredentials result = new XsuaaCredentials();
result.setXsAppName(environment.getProperty("vcap.services.<<xsuaa instance name>>.credentials.xsappname"));
result.setClientId(environment.getProperty("vcap.services.<<xsuaa instance name>>.credentials.clientid"));
result.setClientSecret(environment.getProperty("vcap.services.<<xsuaa instance name>>.credentials.clientsecret"));
result.setUaaDomain(environment.getProperty("vcap.services.<<xsuaa instance name>>.credentials.uaadomain"));
result.setUrl(environment.getProperty("vcap.services.<<xsuaa instance name>>.credentials.url"));
return result;
}
In case RestTemplate
is not configured with an appropriate HttpMessageConverter
the Jwt signature validator can not handle the token keys (JWK set) response from xsuaa. Consequently the JWT signature can not be validated and it may fail with the following error:
JWT verification failed: An error occurred while attempting to decode the Jwt: Couldn't retrieve remote JWK set: org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [class java.lang.String] and content type [application/octet-stream]
In case you use
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
You can configure your xsuaa RestTemplate
like that, e.g. as part of your SecurityConfiguration
configuration class:
@Bean
public RestOperations xsuaaRestOperations() {
RestTemplate restTemplate = new RestTemplate();
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON, MediaType.APPLICATION_OCTET_STREAM));
restTemplate.getMessageConverters().add(mappingJackson2HttpMessageConverter);
return restTemplate;
}
- 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 only intended for JUnit tests.
- Sample
demonstrating how to leverage xsuaa and spring security library to secure a Spring Boot web application including token exchange (user, client-credentials, refresh, ...). Furthermore it documents how to implement SpringWebMvcTests usingjava-security-test
library. - Basic Auth Sample
demonstrating how a user can access Rest API via basic authentication (user/password).