This library provides a Token Validation utility for Jakarta EE applications. Tokens issued by SAP BTP Identity service and Xsuaa services are supported. Moreover, Identity tokens issued by multiple tenants can also be validated.
To be able to validate tokens it performs the following tasks:
- Loads Identity Service Configuration from
VCAP_SERVICES
in Cloud Foundry or from secrets in Kubernetes environment into theOAuth2ServiceConfiguration
. TheEnvironments
serves as central entry point to get access to theOAuth2ServiceConfiguration
. - Decodes and parses encoded JSON Web Tokens into
Token
object which provides convenient getter methods to access token header parameters and claims.Token
is a Java implementation of JSON Web Token (JWT) - RFC 7519. - Validates the decoded token.
- The
JwtValidatorBuilder
gathers all required Jwt validators for specified security service -Xsuaa
orIdentity
. - The
TokenAuthenticator
interface serves as a validation entry point for tokens contained in the authorization header of HTTP requests and has 2 implementations provided: IasAuthenticator and XsuaaAuthenticator. These implementations initialize theJwtValidatorBuilder
for the corresponding security service which returnsCombiningValidator
and then the Token validations is delegated to the Jwt Validators from theCombiningValidator
:JwtTimestampValidator
- checks if the JWT is used before theexp
(expiration) time and if it is used after thenbf
(not before) timeJwtIssuerValidator
(Identity service only) - validates if the JWT is issued by a trustworthy identity serviceXsuaaJkuValidator
(Xsuaa only) - validates if the JWT provides a validjku
token header parameter that points to a JWKS url from a trustworthy identity service. Thisjku
needs to match theuaadomain
from service configuration.JwtAudienceValidator
- checks if the JWT is intended for the OAuth2 client of this application. Theaud
(audience) claim identifies the recipients the JWT is issued for.JwtSignatureValidator
- checks if the JWT is signed with the public key of a trustworthy identity service. With that it also makes sure that the payload and the header of the JWT is unchanged.
- The
- Stores the decoded and validated token in thread-local cache
SecurityContext
.
💡 This library is also integrated in the SAP Java Buildpack, but as of now SAP Java Buildpack does not support Tomcat 10 runtime, which is required for the Jakarta API used by this library.
- Cloud Foundry
- Kubernetes/Kyma
- SAP Identity service, supports Multitenancy/multiple zones
- XSUAA
JWS | Algorithm | Description |
---|---|---|
RS256 | RSA256 | RSASSA-PKCS1-v1_5 with SHA-256 |
- Java 17
- Apache HttpClient 4.5
- Tomcat 10 or any other servlet that implements specification of the Jakarta EE platform
<dependency>
<groupId>com.sap.cloud.security</groupId>
<artifactId>java-security</artifactId>
<version>3.5.5</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
The TokenAuthenticator
makes it easy to integrate token based authentication into your java application.
The library provides 2 default implementations of TokenAuthenticator
interface:
- XsuaaTokenAuthenticator for Xsuaa Access token validation
- IasTokenAuthenticator for Identity OIDC token validation.
XsuaaTokenAuthenticator
and IasTokenAuthenticator
takes care of
OAuth2ServiceConfiguration
loadingorg.apache.http.HttpClient
initialization (it's required for signature validation)- Jwt Validator setup with help of
JwtValidatorBuilder
Once TokenAuthenticator#validateRequest(ServletRequest, ServletResponse)
is called, it'll extract the token from the incoming ServletRequest
and delegate it to the default Jwt validators for corresponding security service.
💡 In case the default authenticators does not fit to your needs it is possible to customize them
The previously mentioned authenticators can be directly initialized in the jakarta.servlet.Filter
implementation as follows
import com.sap.cloud.security.servlet.TokenAuthenticationResult;
import com.sap.cloud.security.servlet.XsuaaTokenAuthenticator;
import com.sap.cloud.security.token.SecurityContext;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.annotation.WebFilter;
@WebFilter("/*")
public class XsuaaSecurityFilter implements Filter {
private final XsuaaTokenAuthenticator xsuaaTokenAuthenticator;
public XsuaaSecurityFilter() {
xsuaaTokenAuthenticator = new XsuaaTokenAuthenticator();
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
try {
TokenAuthenticationResult authenticationResult = xsuaaTokenAuthenticator.validateRequest(request, response);
if (authenticationResult.isAuthenticated()) {
// Do something on successful token validation
} else {
// Do something on invalid token
}
} finally {
SecurityContext.clear();
}
}
}
Access the OAuth2 Service configuration bound to the application:
- For Xsuaa Service
OAuth2ServiceConfiguration serviceConfig = Environments.getCurrent().getXsuaaConfiguration(); OAuth2ServiceConfiguration serviceConfig = Environments.getCurrent().getXsuaaConfigurationForTokenExchange(); // in case of multiple xsuaa bindings, this method returns the broker plan binding
- For Identity service
OAuth2ServiceConfiguration serviceConfig = Environments.getCurrent().getIasConfiguration();
💡 Environments
auto-detects the environment (Cloud Foundry or Kubernetes) the app is deployed in.
Alternatively, you can also specify a custom 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();
Use custom Service Configuration in XsuaaTokenAuthenticator
or IasTokenAuthenticator
as shown below:
XsuaaTokenAuthenticator authenticator = new XsuaaTokenAuthenticator()
.withServiceConfiguration(OAuth2ServiceConfiguration);
💡 OAuth2ServiceConfiguration.getClientIdentity()
offers a convenience method that with OAuth2ServiceConfigurationBuilder
implementation will resolve ClientCredentials
or ClientCertificate
implementations of ClientIdentity
interface based on the credential-type
from the Xsuaa or Identity service binding
and with that providing the correct implementation for the configured authentication type e.g. X.509 or client secret based.
Starting with version 2.13.0, the service bindings are read with btp-environment-variable-access library.
Please adhere to the guidelines outlined here for configuring K8s secrets for the bound service configurations.
Detailed information on how to use java-security
library in Kubernetes/Kyma environment can be found in java-security-usage sample's README.
Generally, the library supports out of the box 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>
- Identity Services:
/etc/secrets/sapbtp/identity/<YOUR IDENTITY SERVICE 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
mountPath
must be defined in environment variableSERVICE_BINDING_ROOT
.- Upon creation of the 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.
- The
SERVICE_BINDING_ROOT
environment variable needs to be defined with a value that points to volume mount's directory (mounthPath
) where the service binding secret will be stored. e.g. like here.
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();
Configure the JwtValidatorBuilder
with the service configuration and other customizable options like in the example below.
CombiningValidator<Token> validators = JwtValidatorBuilder
.getInstance(serviceConfig)
.with(Validator<Token>) // add your custom Validator
.withCacheConfiguration(CacheConfiguration) // with your custom token_key cache configuration
.withValidatorListener(ValidationListener) // for audit logging purpose
.withAudienceValidator(Validator<Token> audienceValidator) // overwrites the default AudienceValidator
.configureAnotherServiceInstance(OAuth2ServiceConfiguration) // in case you want to add another service configuration for which you want to accept tokens too
.build();
💡 Keep in mind that JwtValidatorBuilder
automatically constructs a CombiningValidator
as a singleton, ensuring that only one CombiningValidator is initialized for each OAuth2ServiceConfiguration.
Validate Token:
ValidationResult result = validators.validate(token);
if(result.isErroneous()) {
logger.warn("User is not authenticated: " + result.getErrorDescription());
}
The token keys fetched from the security services are cached for about 10 minutes.
To override the cache, use JwtValidatorBuilder.withCacheConfiguration(customCacheConfiguration)
and provide your own
implementation of the token key cache interface as shown below:
CacheConfiguration customCacheConfiguration = new CacheConfiguration(){
@Override
public Duration getCacheDuration() {
return Duration.ofMinutes(30);
}
@Override
public int getCacheSize() {
return 1000;
}
@Override
public boolean isCacheDisabled() {
return false;
}
@Override
public boolean isCacheStatisticsEnabled() {
return true;
}
};
You can add validation listener to the validators, which will be invoked whenever a token is validated. This can be useful for tasks such as logging to an audit log service. To receive callbacks for successful or failed validations, the validation listener must implement the ValidationListener interface.
To use validation listeners with XsuaaTokenAuthenticator
or IasTokenAuthenticator
, follow the example below:
XsuaaTokenAuthenticator authenticator = new XsuaaTokenAuthenticator().withValidationListener(ValidationListener);
Alternatively, configure validation listeners using JwtValidatorBuilder
:
CombiningValidator<Token> validators = JwtValidatorBuilder
.getInstance(serviceConfig)
.withValidatorListener(ValidationListener) // add your Validation Listener
.build();
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);
Once enabled, it will forward the X509 client certificate from the request header x-fowarded-client-cert
as x-client_cert
header to the /oauth2/token_keys
endpoint.
To enable Proof Token validation for JwtSignatureValidator
:
JwtValidatorBuilder.getInstance(oAuth2ServiceConfiguration)
.enableProofTokenCheck()
.build();
This code snippet decodes a given JSON Web Token (JWT) and extracts its JSON header and payload. The Token
interface
allows for easy access to JWT header parameters and claims. The claim constants can be found in
the TokenClaims
class.
String authorizationHeader="Bearer eyJhbGciOiJGUzI1NiJ2.eyJhh...";
Token token=Token.create(authorizationHeader); // compatible with tokens issued by xsuaa and ias
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);
...
SecurityContext.setToken(token);
You can use java-security-test library for testing the security layer. See the README.md for more information.
When you like to test/debug your secured application rest API locally (offline) you need to provide the VCAP_SERVICES
before you run the application. The security library requires the following key value pairs in the VCAP_SERVICES
- For Xsuaa under
xsuaa/credentials
for jwt validation"uaadomain" : "localhost"
"verificationkey" : "<public key your jwt token is signed with>"
- For Identity service under
identity/credentials
"domains": "localhost"
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 using a REST client such as Postman
and provide the generated JWT token as Authorization
header to access the secured servlets.
A detailed step-by-step description and a sample can be found here.
If you encounter an issue, submit an issue on Github and include the following details:
- Security-related dependencies; obtain the dependency tree with
mvn dependency:tree
- SAP Java buildpack version, e.g., 1.26.1
- Debug logs
- Description of the problem / steps to reproduce.
This depends on the SLF4J implementation you are using (see here for more information). You need to configure the debug log level for the com.sap.cloud.security
package.
💡 Refer to the java-security-usage example to see how to set up the logging level for the SimpleLogger implementation.
You should also increase the logging level in your application. This can be achieved by setting the SET_LOGGING_LEVEL
environment variable for your application. You can include this in your deployment descriptor, such as manifest.yml
, within the env
section like this:
env:
SET_LOGGING_LEVEL: '{com.sap.xs.security: DEBUG, com.sap.cloud.security: DEBUG}'
After updating the deployment descriptor, you must redeploy your app.
For a running application, you can also adjust the logging level using the cf
command line tool:
cf set-env <your app name> SET_LOGGING_LEVEL "{com.sap.xs.security: DEBUG, com.sap.cloud.security: DEBUG}"
💡 Remember to restage your application for the changes to take effect.
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_33
Reference: https://cli.cloudfoundry.org/en-US/v7/app.html
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.
In case of unsuccessful response i.e 401
or 403
error codes, check application error logs for detailed messages.
Common reasons for failed validation:
- invalid X509 certificate ->
CertificateException
is thrown when parsing of X509 certificate failed - X509 certificate is missing from the
SecurityContext
cnf
claim is missing from incoming request- incorrectly configured Validators, e.g. trying to access application with Xsuaa broker issued token, but haven't configured the validators for it
- getting 403 -> User does not have corresponding Role Collection assigned
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 successfully 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>
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.
Use our BoM to keep all the version in sync as demonstrated in this sample.
In case you use the SAP Java Buildpack java-security
is provided. To keep them in sync 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
This sample demonstrates the use of the
java-security
library to perform authentication and authorization checks within a Java application when bound to an Xsuaa service. Furthermore, it documents how to implement JUnit Tests usingjava-security-test
library. - Identity Sample
This sample demonstrates the use of thejava-security
library to perform authentication checks within a Java application when bound to Identity service. Furthermore, it documents how to implement JUnit Tests usingjava-security-test
library. - SAP Java Buildpack Sample this sample showcases the use of the
java-security
library in conjunction with the SAP Java Buildpack.