Token Validation for Java applications.
- Loads Identity Service Configuration from
VCAP_SERVICESin Cloud Foundry or from secrets in Kubernetes environment. TheEnvironmentsserves as central entry point to get or parse theOAuth2ServiceConfigurationwithin SAP Business Technology Platform. - Decodes and parses encoded JSON Web Tokens (
Token) and provides convenient access to token header parameters and claims. A Java implementation of JSON Web Token (JWT) - RFC 7519. - Validates the decoded token. The
JwtValidatorBuildercomprises the following mandatory checks:- Is the JWT used before the
exp(expiration) time and eventually is it used after thenbf(not before) time (JwtTimestampValidator)? - Is the JWT issued by a trust worthy identity service (
JwtIssuerValidator)?
In case of XSUAA does the JWT provide a validjkutoken header parameter that points to a JWKS url from a trust worthy identity service (XsuaaJkuValidator) as it matches the uaa domain? - Is the JWT intended for the OAuth2 client of this application? The
aud(audience) claim identifies the recipients the JWT is issued for (JwtAudienceValidator). - Is the JWT signed with the public key of the trust-worthy identity service? With that it also makes sure that the payload and the header of the JWT is unchanged (
JwtSignatureValidator)?
- Is the JWT used before the
- Provides thread-local cache (
SecurityContext) to store the decoded and validated token. - Furthermore, it provides an authenticator (
TokenAuthenticator) that validates bearer tokens contained in the authorization header of HTTP requests. The authenticator is used in SAP Java Buildpack, as well as in the /samples/java-security-usage*.
- JSON Parser Reference implementation: json.org
- No crypto library. Leverages Public Key Infrastructure (PKI) provided by Java Security Framework to verify digital signatures.
- Cloud Foundry
- Kubernetes/Kyma
- XSUAA
- starting with version
2.8.0IAS - starting with version
2.9.0IAS tokens from multiple tenants and zones
| JWS | Algorithm | Description |
|---|---|---|
| RS256 | RSA256 | RSASSA-PKCS1-v1_5 with SHA-256 |
<dependency>
<groupId>com.sap.cloud.security</groupId>
<artifactId>java-security</artifactId>
<version>2.13.7</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>This library uses slf4j for logging. It only ships the slf4j-api module and no actual logger implementation. For the logging to work slf4j needs to find a valid logger implementation at runtime. If your app is deployed via buildpack then you will have one available and logging should just work.
If there is no valid logger binding at runtime you will see an error message like this:
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
In this case you need to add a logger implementation dependency to your application. See the slf4j documentation for more information and a list of available logger options.
OAuth2ServiceConfiguration serviceConfig = Environments.getCurrent().getXsuaaConfiguration();Note: By default
Environmentsauto-detects the environment: Cloud Foundry or Kubernetes.
Alternatively, you can also specify your own Service Configuration:
OAuth2ServiceConfiguration serviceConfig = OAuth2ServiceConfigurationBuilder.forService(Service.XSUAA)
.withProperty(CFConstants.XSUAA.APP_ID, "appid")
.withProperty(CFConstants.XSUAA.UAA_DOMAIN, "authentication.sap.hana.ondemand.com")
.withUrl("https://paas.authentication.sap.hana.ondemand.com")
.withClientId("oauth-client")
.withClientSecret("oauth-client-secret")
.build();💡 OAuth2ServiceConfiguration.getClientIdentity() is a convenience method that with OAuth2ServiceConfigurationBuilder implementation will resolve ClientCredentials or ClientCertificate implementations of ClientIdentity interface based on the credential-type from the Xsuaa service binding. Therefore, providing the correct implementation for the configured authentication type e.g. X.509 or client secret based.
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 java-security library in Kubernetes/Kyma environment can be found in java-security-usage sample README.
Now configure the JwtValidatorBuilder once with the service configuration from the previous step.
CombiningValidator<Token> validators = JwtValidatorBuilder.getInstance(serviceConfig).build();Note: By default
JwtValidatorBuilderbuilds aCombiningValidator.
For the Signature validation it needs to fetch the Json Web Token Keys (jwks) from the OAuth server. In case the token does not provide a
jkuheader parameter it also requests the Open-ID Provider Configuration from the OAuth Server to determine thejwks_uri. The used Apache Rest client can be customized via theJwtValidatorBuilderbuilder.
Furthermore, the token keys fetched from the Identity Service are cached for about 10 minutes. You may like to overwrite the cache default configuration with
JwtValidatorBuilder.withCacheConfiguration().
Optionally, you can add a validation listener to the validator to be able to get called back whenever a token is validated. Here you may want to emit logs to the audit log service.
JwtValidatorBuilder.getInstance(serviceConfig).withValidatorListener(validationListener);The validation listener needs to implement the ValidationListener interface to be able to receive callbacks on validation success or failure.
This decodes an encoded JSON Web Token (JWT) and parses its json header and payload. The Token interface provides a simple access to its JWT header parameters and its claims. You can find the claim constants in the (TokenClaims) class.
String authorizationHeader = "Bearer eyJhbGciOiJGUzI1NiJ2.eyJhh...";
Token token = Token.create(authorizationHeader); // supports tokens issued by xsuaa and iasValidationResult result = validators.validate(token);
if(result.isErroneous()) {
logger.warn("User is not authenticated: " + result.getErrorDescription());
}SecurityContext.setToken(token);Token token = SecurityContext.getToken();
String email = token.getClaimAsString(TokenClaims.EMAIL);
List<String> scopes = token.getClaimAsStringList(TokenClaims.XSUAA.SCOPES);
java.security.Principal principal = token.getPrincipal();
Instant expiredAt = token.getExpiration();
String keyId = token.getHeaderParameterAsString(TokenHeader.KEY_ID);
...In case you need further details from VCAP_SERVICES system environment variable, which are not exposed by OAuth2ServiceConfiguration interface you can use the DefaultJsonObject class for Json parsing.
Example:
String vcapServices = System.getenv(CFConstants.VCAP_SERVICES);
JsonObject serviceJsonObject = new DefaultJsonObject(vcapServices).getJsonObjects(Service.XSUAA.getCFName()).get(0);
Map<String, String> xsuaaConfigMap = serviceJsonObject.getKeyValueMap();
Map<String, String> credentialsMap = serviceJsonObject.getJsonObject(CFConstants.CREDENTIALS).getKeyValueMap();The servlet authenticator part of this library makes it easy to integrate token based authentication into your java application.
For the integration of different Identity Services the TokenAuthenticator interface was created. Right now there are these implementations:
Depending on the application's needs the
TokenAuthenticatorcan be customized.
JwtX5tValidator offers JWT Certificate Thumbprint X5t confirmation method's validation. See specification here.
This validator is not part of the default CombiningValidator, it needs to be added manually to JwtValidatorBuilder to use it.
It can be done in the following manner:
JwtValidatorBuilder.getInstance(oAuth2ServiceConfiguration)
.with(new JwtX5tValidator(oAuth2ServiceConfiguration))
.build();Or it can be used as a standalone Validator, by creating a new instance of it and calling JwtX5tValidator.validate(Token token) method with the token to be validated as a method's parameter. See here how to get a token from SecurityContext
JwtX5tValidator validator = new JwtX5tValidator(oAuth2ServiceConfiguration);
ValidationResult result = validator.validate(token);In case of invalid response i.e 401 or 403 error codes, check application error logs for detailed messages.
Common reasons for failed validation:
- invalid X509 certificate ->
CertificateExceptionis thrown when parsing of X509 certificate failed - X509 certificate is missing from the
SecurityContext cnfclaim is missing from incoming request
You can find the JUnit test utilities documented here.
When you like to test/debug your secured application rest API locally (offline) you need to provide custom VCAP_SERVICES before you run the application. The 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.
You can use the JWTGenerator, which is provided with java-security-test test library.
Now you can test the service manually in the browser using a REST client such as Postman chrome plugin and provide the generated JWT token as Authorization header to access the secured functions.
A detailed step-by-step description and a sample can be found here.
In case you face issues, file an issue on Github and provide these details:
- security related dependencies, get dependency tree with
mvn dependency:tree - (SAP) Java buildpack version, e.g. 1.26.1
- debug logs
- issue you’re facing / steps to reproduce.
The buildpack being used is defined in your deployment descriptor e.g. as part of the manifest.yml file via the
buildpacks attribute.
If it is set to sap_java_buildpack then the newest available version of the SAP Java buildpack is used.
Use command cf app <app-name> to get the exact version of sap_java_buildpack:
Showing health and status for app <app-name> in org ... / space ... as ...
name: <app-name>
requested state: started
routes: ...
last uploaded: Thu 06 May 14:31:01 CEST 2021
stack: cflinuxfs3
buildpacks:
sap_java_buildpack_1_33Reference: https://cli.cloudfoundry.org/en-US/v7/app.html
This depends on the SLF4J implementation, you make use of (see also here). You have to set the debug log level for this package com.sap.cloud.security.
💡 See java-security-usage example for SimpleLogger implementation's logging level setup.
You should also increase the logging level in your application. This can be done by setting the SET_LOGGING_LEVEL
environment variable for your application. You can do this as part of your deployment descriptor such as manifest.yml with the env section like so:
env:
SET_LOGGING_LEVEL: '{com.sap.xs.security: DEBUG, com.sap.cloud.security: DEBUG}'After you have made changes to the deployment descriptor you need do re-deploy your app.
For a running application this can also be done with the cf command line tool:
cf set-env <your app name> SET_LOGGING_LEVEL "{com.sap.xs.security: DEBUG, com.sap.cloud.security: DEBUG}"You need to restage your application for the changes to take effect.
This module requires the JSON-Java library.
If you have classpath related issues involving JSON you should take a look at the Troubleshooting JSON class path issues document.
SecurityContext caches only sucessfully validated tokens thread-locally, i.e. within the same thread. Please increase the log level as described here in order to check whether the token validation fails and for which reason.
In case you use SAP Java Buildpack for token validation, make sure that your J2EE Servlet is annotated with a scope check, like:
@ServletSecurity(@HttpConstraint(rolesAllowed = { "yourScope" }))Or, alternatively in src/main/webapp/WEB-INF/web.xml:
<web-app...
<security-constraint>
<web-resource-collection>
<web-resource-name>All SAP Cloud Platform users</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>YourScope</role-name>
</auth-constraint>
</security-constraint>
</web-app>In case your application provides no scopes, consider the documentation here.
java.util.ServiceConfigurationError: com.sap.cloud.security.token.TokenFactory: Provider com.sap.cloud.security.servlet.HybridTokenFactory not a subtype
Starting with version 2.8.3 the version of java-api needs to match the version of java-security client library. In case you use the SAP Java Buildpack java-security is provided. To keep them in synch its recommended to use SAP Java Buildpack BoM of the respective SAP Java Buildpack version and as done in the sap-java-buildpack-api-usage sample.
- JSON Web Token
- OpenID Connect Core 1.0 incorporating errata set 1
- OpenID Connect Core 1.0 incorporating errata set 1 - ID Token Validation
-
Xsuaa Sample
demonstrating how to leveragejava-securitylibrary to perform authentication and authorization checks within a Java application when bound to a xsuaa service. Furthermore it documents how to implement JUnit Tests usingjava-security-testlibrary. -
Ias Sample
demonstrating how to leveragejava-securitylibrary to perform authentication checks within a Java application when bound to a ias identity service. Furthermore it documents how to implement JUnit Tests usingjava-security-testlibrary.

