Token Validation for Java applications.
- Loads Identity Service Configuration from
VCAP_SERVICES
environment. TheEnvironments
serves as central entry point to get or parse theOAuth2ServiceConfiguration
within SAP Cloud 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
JwtValidatorBuilder
comprises 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 validjku
token 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
- Planned: Kubernetes
- XSUAA
- as of version
2.8.0
IAS
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.8.4</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
Environments
auto-detects the environment: Cloud Foundry or Kubernetes.
Alternatively you can also specify the Service Configuration by your own:
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();
Now configure the JwtValidatorBuilder
once with the service configuration from the previous step.
CombiningValidator<Token> validators = JwtValidatorBuilder.getInstance(serviceConfig).build();
Note: By default
JwtValidatorBuilder
builds 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 ajku
header 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 theJwtValidatorBuilder
builder.
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 = new XsuaaToken(authorizationHeader);
ValidationResult 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
TokenAuthenticator
can be customized.
XsuaaTokenAuthenticator
supports seamless token exchange between IAS and Xsuaa. Token exchange between IAS and Xsuaa means that calling a web application endpoint with an IAS Token will work like calling the endpoint with Xsuaa Token. This functionality is disabled by default.
Requirement for token exchange is token-client dependency with all its' transitive dependencies(shouldn't be excluded) in the project.
<dependency>
<groupId>com.sap.cloud.security.xsuaa</groupId>
<artifactId>token-client</artifactId>
</dependency>
Steps to enable token exchange:
- Set environment variable IAS_XSUAA_XCHANGE_ENABLED to any value except false or empty
- Make sure token-client is not excluded from the project
The authenticator is used in the following sample.
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 buildpacks
to get the exact version of sap_java_buildpack
:
buildpack position enabled locked filename stack
java_buildpack 2 true false java_buildpack-cached-cflinuxfs3-v4.31.1.zip cflinuxfs3
.
.
.
sap_java_buildpack 12 true false sap_java_buildpack-v1.26.1.zip
sap_java_buildpack_1_26 13 true false sap_java_buildpack-v1.26.1.zip
sap_java_buildpack_1_25 14 true false sap_java_buildpack-v1.25.0.zip
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
.
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.
- 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-security
library 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-test
library. -
Ias Sample
demonstrating how to leveragejava-security
library 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-test
library.