This repository offers a comprehensive set of libraries designed to simplify the integration of SAP Business Technology Platform (SAP BTP) security services (XSUAA and Identity Services). Tailored to support Jakarta EE and Spring Boot applications running on Cloud Foundry or Kubernetes environments. The libraries focus on streamlining OAuth 2.0 access token validation for tokens issued by XSUAA and Identity Services. In addition, it offers a token-client library to easily fetch tokens without cumbersome setup for http requests. Finally, it offers testing utility that mocks Xsuaa and Identity service behaviour and makes it easy to write integration and unit tests.
Supported platform: SAP BTP only These libraries are designed and tested exclusively for applications running on SAP Business Technology Platform (SAP BTP) — Cloud Foundry and Kubernetes (Kyma) environments. Service bindings, environment discovery (
Environments.getCurrent()), and all documented usage patterns assume that service credentials are injected by the SAP BTP runtime. Usage outside of SAP BTP (e.g. on-premise, non-SAP cloud, or custom infrastructure) is not supported. Correct behaviour in such environments cannot be guaranteed, and security properties may not hold.
Major version upgrade with breaking changes - upgraded to Spring Boot 4.x and Jakarta EE 10!
✅ Migrating from 3.x? We've got you covered:
- Spring Boot 4.x users: Update to
resourceserver-security-spring-boot-starter4.x - Spring Boot 3.x users: Use new
resourceserver-security-spring-boot-3-starter4.x - same features, full compatibility - spring-xsuaa users: Module removed - follow our migration guide
Key Changes:
- 🚀 Spring Boot 4.x, Spring Framework 7.x, Spring Security 7.x
- 📦 New Spring Boot 3.x compatibility layer via legacy modules
- ⚡ Token-client now uses Java 11 HttpClient by default (Apache HttpClient support via custom integration)
- ❌ Removed deprecated modules:
spring-xsuaa,spring-xsuaa-test,spring-xsuaa-it,spring-security-compatibility
See CHANGELOG.md for complete details.
- What's New in 4.x
- Prerequisites
- Usage
- Installation
- Migration Guide
- Troubleshooting
- Common Pitfalls
- Contributing
- How to get support
- License
Before you can use the SAP Cloud Security Services Integration libraries, you must fulfil the following requirements:
- Knowledge of Java programming and (Optional) Spring Boot framework.
- Access to an SAP BTP account and the XSUAA or Identity service.
- Familiarity with OAuth 2.0 and JWT (JSON Web Tokens).
- Java 17
- Maven 3.9.0 or later
- (Optional) Spring Boot 4.x or later, Spring Security 7.x or later if using the Spring integration
- Spring Boot 3.x users: Use the legacy compatibility modules (see Spring Boot 3.x Compatibility)
❗ For Java 8 and 11 please use 2.x release of this library.
Typical web applications consist of a gateway server serving HTML content to the user client and one or more servers behind the gateway providing REST APIs. The gateway server acts as OAuth2 client executing an OAuth2 Authorization Code Flow to retrieve an access token when a new user client session is created. Requests from the user client are correlated with a session id on the gateway server which appends the access token to subsequent requests and forwards them to the REST APIs. The session flow looks as follows:
- A user accesses the web application using a browser or mobile device which begins a new server session.
- The web application redirects the user client to the OAuth2 server for authentication. In typical SAP Business Technology Platform scenarios, this is handled by an application router. Upon authentication, the web application receives an authorization code from the user client issued by the OAuth2 server.
- An access token is retrieved from the OAuth2 server in exchange for the authorization code.
- The web application uses the access token to access resources on an OAuth2 resource server via a REST API. The OAuth2 resource server validates the token using online or offline validation to restrict access to the API.
OAuth2 resource servers (as the one in step 4) require libraries for validating access tokens.
Key features:
- Automatic OAuth2 service configuration based on SAP BTP service bindings found in the environment
- OAuth2 Token Validation based on these service configurations
- Easy access to principal and token claims within request handlers
- Automatic or sample integrations for common web application frameworks (i.e. Jakarta EE / Spring Security)
Developers who need OAuth2 token validation and token access in their Jakarta EE applications can utilize the java-security library. This library simplifies the process of acquiring token information such as principal and audiences from the security context and takes over token validation for tokens issued by Xsuaa or Identity services. This library is also integrated in SAP Java Buildpack.
In the table below you'll find links to detailed information.
| Library | Usage Examples |
|---|---|
| java-security | java-security-usage demonstrates java-security usage with Xsuaa service java-security-usage-ias demonstrates java-security usage with Identity service sap-java-buildpack-api-usage demonstrates java-security usage with SAP Java Buildpack |
💡 Changes with SAP Java Buildpack 1.26.0
The former SAP Java Buildpack versions have used deprecated (Spring) Security libraries and had to be updated. As of version 1.26.0 SAP Java Buildpack uses the java-security library. Please consider these (migration) guides:
Developers seeking OAuth2 token validation and access to token information for their Spring Boot applications can benefit from the spring-security library. This library streamlines the process of handling token validation for tokens issued by Xsuaa or Identity services and obtaining token details, such as principal and audiences from the security context.
| Your Spring Boot Version | Recommended Module | Description |
|---|---|---|
| Spring Boot 4.x | spring-security with resourceserver-security-spring-boot-starter |
Current version targeting Spring Boot 4.x, Spring Security 7.x |
| Spring Boot 3.x | spring-security-3 with resourceserver-security-spring-boot-3-starter |
Compatibility layer for Spring Boot 3.5.9, Spring Security 6.5.7 |
💡 New in 4.x: The Spring Boot 3 compatible starter provides a seamless migration path for applications that cannot immediately upgrade to Spring Boot 4.x. Both starters provide the same features and APIs.
If your application uses Spring Boot 3.x and you cannot immediately upgrade to Spring Boot 4.x:
<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-3-starter</artifactId>
<version>4.2.0</version>
</dependency>See spring-security-3 README for complete documentation.
spring-xsuaa module has been removed in version 4.x.
If you're using the deprecated spring-xsuaa module:
- Migrate to spring-security (Spring Boot 4.x) or spring-security-3 (Spring Boot 3.x)
- Follow the migration guide for step-by-step instructions
In the table below you'll find links to detailed information.
| Library | Spring Boot Version | Usage Examples |
|---|---|---|
| spring-security | 4.x | spring-security-hybrid-usage demonstrates usage of xsuaa and Identity service token validation |
| spring-security-3 | 3.x | spring-webflux-security-hybrid-usage demonstrates WebFlux usage with the Spring Boot 3 starter |
| spring-security-basic-auth | 4.x | Demonstrates how a user can access Rest API via basic authentication (user/password) |
Java applications that require access tokens (JWT) from Xsuaa or Identity services can utilize the Token Flows API from the token-client library, to fetch JWT tokens for their clients (applications) or users.
Typical use cases:
- technical user / system tokens for service to service communication
- user token exchange for principal propagation in service to service communication
💡 New in 4.x: Token-client now uses Java 11 HttpClient by default. Spring-based implementations have been moved to token-client-spring. For custom HTTP client implementations (Apache HttpClient 4.x/5.x), see CUSTOM_HTTPCLIENT.md.
In the table below you'll find links to detailed information.
| Library | Usage Examples |
|---|---|
| token-client | java-tokenclient-usage demonstrates usage of token client library in Jakarta EE application spring-security-hybrid-usage demonstrates usage in Spring Boot application |
| token-client-spring | Spring-based implementations of OAuth2 token services for Spring Boot 4.x. Required if you use XsuaaOAuth2TokenService, SpringOAuth2TokenKeyService, or SpringOidcConfigurationService. |
| token-client-spring-3 | Spring-based implementations of OAuth2 token services for Spring Boot 3.x. Use this instead of token-client-spring if your application uses Spring Boot 3.x. |
For authentication/authorization flow testing purposes there is java-security-test library at your disposal that can be used for unit and integration tests to test the Xsuaa or Identity service client functionality in the application. It provides a JwtGenerator to generate custom JWT tokens that work together with a pre-configured WireMock web server that stubs outgoing calls to the Identity or Xsuaa service, e.g to fetch the JWKS used to check the validity of the token signature. With this library you can test end to end all your secured endpoints or app logic that is dependant on information from the tokens.
Key features:
- Generates and signs tokens with user provided attributes
- Provides a pre-configured local authorization server that mocks communication with the BTP security services to validate self-generated tokens
- For Jakarta EE application sets up a local application server that is pre-configured with a security filter matching self-generated tokens. It can be configured to serve the servlets you want to test with mocked authorization
In the table below you'll find links to detailed information.
| Library | Usage Examples |
|---|---|
| java-security-test | Integration test code snippet for Spring application Integration test code snippet for Jakarta EE web.xml based servlets Integration test code snippet for Jakarta EE annotation based servlets |
In hybrid authentication setups, your application can accept tokens from both SAP Identity Authentication Service ( IAS) and XSUAA simultaneously. This approach eases migration from XSUAA to IAS by exchanging IAS user tokens for XSUAA tokens behind the scenes.
Goal: Maintain backward compatibility during migration. Users authenticate via IAS, but the application continues using XSUAA-based authorization (scopes, role collections).
IAS User Tokens Only: Token exchange only applies to end-user tokens from IAS. Client credentials tokens are not exchanged. Attempting exchange on technical tokens will result in errors.
Performance: Exchanged tokens are cached per request and reused until expiration. Caching is automatic and requires no configuration.
1. Request arrives with Authorization: Bearer <token>
2. Library identifies issuer (IAS vs XSUAA) from token claims
3. Token validated against appropriate identity service
4. [IF IAS token + exchange enabled]
├─ Obtain strong IAS ID token (if access token provided)
├─ Call XSUAA /oauth/token endpoint with JWT bearer grant
└─ Store exchanged XSUAA token in SecurityContext
5. [IF exchange disabled OR XSUAA token]
└─ Use validated token directly
6. The XSUAA token contains the user's roles/scopes as defined in XSUAA
7. Authorization proceeds using familiar XSUAA token attributes
If the incoming token is already an XSUAA token, no exchange occurs—it's validated and used directly. The XSUAA token will then be available in the security context as usual via getToken() as well as via getXsuaaToken().
Failure Handling: If token exchange fails (network issues, misconfiguration), authentication fails with 401 Unauthorized. No silent fallback occurs since IAS access tokens typically lack scopes needed for authorization.
The TokenExchangeModeenum controls
when
and how IAS tokens are exchanged:
| Mode | Behavior |
|---|---|
DISABLED |
No exchange. Each token type is validated and used as-is. |
PROVIDE_XSUAA |
IAS token is validated and exchanged for XSUAA, but the IAS token remains primary in the security context. The XSUAA token is accessible via SecurityContext.getXsuaaToken(). |
FORCE_XSUAA |
IAS token is exchanged for XSUAA and the XSUAA token replaces the IAS token in the security context. The resulting security context looks as if an XSUAA token had been received directly. |
The Initial token is still available via the SecurityContext.getInitialToken() getter and the ID token is available with SecurityContext.getIdToken()
Mode Selection Guide:
- Use
PROVIDE_XSUAAwhen the app is migrated to AMS authorization and wants to offer combined XSUAA and AMS authorizations for migrated tenants (requires additional configuration of the AMS client library) - Use
FORCE_XSUAAfor maximum backward compatibility—the app operates based on XSUAA tokens like before - Use
DISABLEDor remove the property completely after completing the migration to IAS
- Both XSUAA and IAS service bindings must be configured
- IAS service binding must include
xsuaa-cross-consumption: trueparameter - Ensure XSUAA trusts the IAS identity provider
2.4.1 Jakarta Example: Using HybridTokenAuthenticator
For Jakarta EE applications, use
HybridTokenAuthenticator in
a servlet filter.
For more information, see the HybridTokenAuthenticator Javadoc.
2.4.2 Spring Boot Example: Using HybridJwtDecoder
For Spring Boot applications,
HybridIdentityServicesAutoConfiguration
automatically configures hybrid authentication when both IAS and XSUAA bindings are detected.
For more information, see the HybridJwtDecoder Javadoc.
Common issues and solutions:
| Issue | Cause | Solution |
|---|---|---|
Token exchange failed exception |
Missing XSUAA binding or invalid configuration | Verify both IAS and XSUAA service bindings exist |
| Exchange returns 401 | IAS binding missing xsuaa-cross-consumption |
Add parameter to IAS service binding |
The library keeps three internal caches that shape latency and identity-service load:
| Cache | Namespace | What it caches | Default |
|---|---|---|---|
| Outbound token cache | tokens |
client-credentials, refresh-token, and JWT-bearer token-exchange responses from XSUAA / IAS | Caffeine in-memory |
| JWKS cache | jwks |
raw JWKS JSON per JWKS URI | Caffeine in-memory |
| OIDC discovery cache | oidc |
discovery response per issuer | Caffeine in-memory |
The outbound token cache covers every token that goes through OAuth2TokenService — including the IAS-to-XSUAA and XSUAA-to-XSUAA exchanges triggered via DefaultIdTokenExtension / DefaultXsuaaTokenExtension. There is no separate exchange cache; the same shared store serves all three flows.
Default (Caffeine in-memory) is the right choice for:
- Small or single-instance services.
- Services where a cold start after a restart is acceptable (a few seconds of extra XSUAA / IAS traffic while caches warm up).
- Local development and tests.
Switch to a distributed cache (e.g. Redis) for:
- Larger deployments with many pods — every additional pod would otherwise fetch JWKS and issue its own client-credentials / exchange tokens independently.
- Services with frequent rolling deploys — every deploy takes every pod through a cold cache, so the identity-service load scales with your deploy frequency.
- Token-exchange-heavy workloads (e.g. IAS-to-XSUAA exchange on every incoming user request) — a single hot tenant amplifies pod-local cache misses into a burst on the identity service.
- Any service that must stay within the per-subaccount XSUAA rate limit under peak traffic and rolling deploys.
Sharing these caches across pods eliminates the cold-start penalty, keeps identity-service traffic flat during deploys, and stops the classic thundering herd where every pod races XSUAA for the same client-credentials token.
All three caches share the same runtime semantics:
-
Best-effort contract. A cache is an optimization, never a failure cause. Every cache operation is wrapped: any adapter error degrades to a cache miss and a WARN log, and token retrieval, JWKS fetch, and validation continue against the identity service. A down or misconfigured store costs you the shared-cache benefit — it never rejects or breaks requests.
-
Single-flight. Concurrent misses for the same cache key are collapsed into one upstream call (JWKS fetch, token request, or discovery call); all other callers wait for that single result and only the fetcher writes to the cache. This prevents the thundering herd when many requests miss at the same time (cold start, cache expiry). Note: the waiters block for the duration of the single in-flight call, bounded by the HTTP client's socket timeout (30 seconds) — see Known limitations.
-
Negative caching (JWKS). A JWKS fetch that fails with a stable client error (any 4xx response, e.g. a 404 for an unknown zone id) is cached as a short-lived negative entry (30 seconds). Within that window, further requests for the same key fail fast with an exception instead of each making a network call, so a stream of invalid tokens cannot keep triggering calls to the identity service. Transient failures — server errors (5xx), timeouts, connection problems — are not negatively cached, so the next request retries immediately. After a negative entry expires, the next request retries the retrieval.
-
Per-entry TTLs. Every
setcarries a per-entry TTL (default 5 minutes for tokens, 10 minutes for JWKS/OIDC, 30 seconds for negative entries). How far the adapter honors it:Adapter Per-entry TTL CaffeineSecurityCachehonored — shorter TTLs (negative entries) expire independently of the cache-wide default SpringCacheSecurityCachedelegated to the store's own expiration policy (e.g. Redis entryTtl)custom adapters yours to implement; ttl == nullmeans "use your adapter default" -
Token entries are expiry-checked on every read. Beyond the cache TTL, a cached outbound token is re-checked against its
expclaim (with a small expiration delta) on every read, so a token is never returned once it is about to expire — even if the store's TTL is long.
All caches route through a single com.sap.cloud.security.cache.SecurityCache<String,String> SPI. The library ships two adapters out of the box:
CaffeineSecurityCache(intoken-client) — the in-process default; used when you do nothing. Honors per-entry TTLs (e.g. short-lived negative-cache entries) in addition to its cache-wide default. Perfect for small services.SpringCacheSecurityCache(inspring-security/spring-security-3) — wraps a Springorg.springframework.cache.Cacheso any Spring-managed backend (Redis, Hazelcast, Caffeine, ...) works via Spring'sCacheManagerabstraction.
Any other backend (a raw Jedis client, a Hazelcast IMap, a JDBC-backed store, ...) is a copy-pasteable implementation of the SPI — see Bring your own cache below.
Enable the auto-configuration and expose a Spring CacheManager whose cache is named sap-security (or override the name via sap.security.cache.distributed.cache-name):
sap:
security:
cache:
distributed:
enabled: true
# cache-name: sap-security # defaultimport java.time.Duration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
@Configuration
public class DistributedCacheConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory rcf) {
return RedisCacheManager.builder(rcf)
.initialCacheNames(Set.of("sap-security"))
.withInitialCacheConfiguration(
"sap-security",
RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10))) // must be finite — see Warnings
.build();
}
}The auto-configuration discovers, in order: (1) a user-supplied SecurityCache<String,String> bean → (2) a Spring CacheManager cache with the configured name. If neither is present the context fails fast with a clear error rather than silently disabling caching.
Instantiate a SecurityCache adapter and hand it to the token service and the validator builder:
// 1. Build a SecurityCache backed by your store (see snippets below).
SecurityCache<String, String> cache = new MyRedisSecurityCache(jedisPool);
// 2. Share it with the outbound token cache.
OAuth2TokenService tokenService = new DefaultOAuth2TokenService(
httpClient,
TokenCacheConfiguration.defaultConfiguration(),
cache);
// 3. Share it with the JWKS + OIDC caches during token validation.
CombiningValidator<Token> validator = JwtValidatorBuilder.getInstance(config)
.withHttpClient(httpClient)
.withSecurityCache(cache)
.build();Spring Boot properties (both spring-security and spring-security-3 modules):
| Property | Default | Meaning |
|---|---|---|
sap.security.cache.distributed.enabled |
false |
Switches the auto-configuration on. false keeps the in-memory Caffeine default (no behavior change). |
sap.security.cache.distributed.cache-name |
sap-security |
Name of the Spring CacheManager cache the auto-configuration wraps. |
Plain-Java wiring points (no properties — you pass the cache explicitly):
| Component | Cache wired into | How |
|---|---|---|
DefaultOAuth2TokenService |
outbound token cache (tokens) |
3-arg constructor (..., SecurityCache) |
JwtValidatorBuilder |
JWKS + OIDC caches (jwks, oidc) |
.withSecurityCache(SecurityCache) |
AbstractTokenAuthenticator (servlet API) |
JWKS + OIDC caches of the built validator | .withSecurityCache(SecurityCache) |
On Spring Boot the auto-configuration performs all of the wiring above for you (token flows, JwtValidatorBuilder, the JwtDecoder, and the hybrid auto-configurations) — you only provide the SecurityCache bean or the CacheManager.
All entries live in one namespace-prefixed key space, so a single shared store can hold all three caches and clear() only touches library-owned keys:
| Cache | Key format | Stored value | Default TTL |
|---|---|---|---|
| Outbound tokens | tokens:<sha256 hex of the request fingerprint> |
the token response as JSON (access_token, refresh_token, expiry, ...) |
5 min |
| JWKS | jwks:url:<jwks uri>|<request params> e.g. jwks:url:https://ias.example/token_keys|x-app-tid:abc |
the raw JWKS JSON exactly as returned by the identity service. A negative entry stores the fixed marker jwks-fetch-failed instead |
10 min |
| OIDC discovery | oidc:<discovery endpoint uri> |
compact JSON envelope with token_endpoint, authorization_endpoint, jwks_uri |
10 min |
Design choices behind the key scheme:
- Token keys are opaque on purpose. The request fingerprint contains the client secret, so it is SHA-256 hashed before it becomes a key — no secret material ever appears in the key space, in store listings, or in logs.
- JWKS/OIDC keys are human-readable on purpose. They carry URIs and non-secret request parameters only, so you can inspect, audit, and delete specific entries in your store (e.g. all
jwks:*entries after an emergency key revocation). - Values are raw strings, not serialized library objects. Entries are plain JSON that is re-parsed on every hit, which is what makes them safe to share across processes (and, eventually, across the Java and Node.js libraries). The cost is a small, predictable per-hit CPU overhead.
The SPI has four methods: get, set, delete, clear. Two invariants matter:
- Never throw. Every cache call is best-effort. A cache failure must degrade to a miss and let the caller fetch from source — swallow the exception, log at WARN, return.
- Thread-safe. The library calls the cache from many threads concurrently.
import com.sap.cloud.security.cache.SecurityCache;
import jakarta.annotation.Nonnull;
import java.time.Duration;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;
public final class JedisSecurityCache implements SecurityCache<String, String> {
private static final Logger LOG = LoggerFactory.getLogger(JedisSecurityCache.class);
private static final String PREFIX = "sap-security:";
private static final long DEFAULT_TTL_SECONDS = 600; // 10 min
private final JedisPool pool;
public JedisSecurityCache(final JedisPool pool) {
this.pool = pool;
}
@Override @Nonnull
public Optional<String> get(@Nonnull final String key) {
try (Jedis j = pool.getResource()) {
return Optional.ofNullable(j.get(PREFIX + key));
} catch (RuntimeException e) {
LOG.warn("Redis.get failed for {}: {}", key, e.getMessage());
return Optional.empty();
}
}
@Override
public void set(@Nonnull final String key, @Nonnull final String value, final Duration ttl) {
long seconds = ttl != null ? Math.max(1, ttl.getSeconds()) : DEFAULT_TTL_SECONDS;
try (Jedis j = pool.getResource()) {
j.set(PREFIX + key, value, SetParams.setParams().ex(seconds));
} catch (RuntimeException e) {
LOG.warn("Redis.set failed for {}: {}", key, e.getMessage());
}
}
@Override
public void delete(@Nonnull final String key) {
try (Jedis j = pool.getResource()) {
j.del(PREFIX + key);
} catch (RuntimeException e) {
LOG.warn("Redis.del failed for {}: {}", key, e.getMessage());
}
}
@Override
public void clear() {
// Only wipe our own keys — never FLUSHDB a shared Redis.
// SCAN (not KEYS) so a large store is not blocked.
try (Jedis j = pool.getResource()) {
String cursor = "0";
do {
var scanResult = j.scan(
cursor, new redis.clients.jedis.params.ScanParams().match(PREFIX + "*").count(100));
j.del(scanResult.getResult().toArray(new String[0]));
cursor = scanResult.getCursor();
} while (!"0".equals(cursor));
} catch (RuntimeException e) {
LOG.warn("Redis.clear failed: {}", e.getMessage());
}
}
}Wire it up as shown in Enabling on plain Java.
If your app already has a Spring CacheManager you do not need to implement SecurityCache yourself. Just make sure the manager exposes a cache named sap-security — the auto-configuration wraps it via SpringCacheSecurityCache:
@Configuration
public class SecurityCacheConfig {
// Hazelcast — Spring will discover it via HazelcastCacheManager.
@Bean
public CacheManager cacheManager(HazelcastInstance hz) {
return new HazelcastCacheManager(hz); // needs an IMap named "sap-security"
}
}For a fully custom backend (a JDBC store, an in-house cache, ...) you can also expose a SecurityCache<String,String> bean directly — the auto-configuration prefers that over any CacheManager:
@Bean
public SecurityCache<String, String> securityCache(JedisPool pool) {
return new JedisSecurityCache(pool); // from Snippet 1
}Verify the shared cache is actually in use:
- Spring Boot: the auto-configuration logs
Wiring SecurityCache to Spring CacheManager cache '<name>'at startup (INFO). - Inspect the store: entries appear under the
tokens:*,jwks:*, andoidc:*key spaces (plus your adapter's own prefix, e.g.sap-security:in the Jedis snippet). - Cross-pod round-trip: restart one pod and watch it serve warm entries instead of re-fetching — identity-service call volume stays flat across a rolling deploy.
Troubleshooting:
| Symptom | Cause | Resolution |
|---|---|---|
Context fails with sap.security.cache.distributed.enabled=true but no backing store was found |
Property enabled, but no SecurityCache bean and no CacheManager cache with the configured name |
Provide a SecurityCache bean, fix the cache-name, or set enabled=false to keep the in-memory default |
Repeated WARNs SecurityCache.get/set failed ... |
Store unreachable, timing out, or deserialization error | Check store health, network, and timeouts. The library continues without the cache (best-effort) — correctness is unaffected, only the shared-cache benefit is lost |
Validation fails with Key with kid ... not found after an identity-service key rotation |
Store TTL is unbounded, so the cached JWKS never expires (see Warnings) | Configure a finite store TTL, or delete the affected jwks:* keys / call SecurityCache.clear() to force a refresh |
| Every request with an invalid token triggers a call to the identity service | Library version before 4.2.0 (no negative caching) | Upgrade to 4.2.0: JWKS fetches that fail with a 4xx are negatively cached for 30 s and fail fast |
| Validations for one key are delayed during an identity-service slowdown | Single-flight waiters block on the single in-flight refresh (max 30 s socket timeout) | Expected behavior; a non-blocking background refresh is being evaluated as a follow-up (see Known limitations) |
- The store holds secrets. Token entries contain access and refresh tokens as plaintext JSON. Treat the cache store as confidential: restrict access (TLS, ACLs, private network / VPC-only), and keep library entries in their own namespaced/prefixed key space so you can audit and wipe them without touching other data.
- Keys and logs are log-safe by design. Token fingerprints are SHA-256 hashes; JWKS/OIDC keys carry URIs and non-secret request parameters only. Library log statements for cache operations never include tokens, cache keys, or store error payloads.
- Revocation semantics. JWKS entries are refreshed within one TTL period (default 10 minutes), so a key revocation propagates within that window. To force immediate propagation, delete the affected
jwks:*keys or callSecurityCache.clear().
- Failure semantics. Every cache call is best-effort. A broken cache never breaks token retrieval, JWKS fetch, or token validation — the library falls through to the source of truth and logs a WARN.
- The store's TTL must be finite. The library passes a per-entry TTL with every
set, but the shippedSpringCacheSecurityCache(and any adapter that delegates to infrastructure-managed expiration) lets the store's own policy decide. If your store has no expiration (e.g. aRedisCacheManagerwithoutentryTtl, or a plainSETwithoutEXPIRE), JWKS and OIDC entries live forever: they are only refetched on a miss, so after an identity-service key rotation the cached JWKS is never refreshed and token validation starts failing withKey with kid ... not found. Configure the store's TTL at or below the library's cache duration (default 10 minutes). Outbound-token entries are additionally re-checked against the token'sexpclaim on every read, but JWKS / OIDC entries are not. - Time-to-live and rotation. With a finite store TTL, a key rotation is picked up within one TTL period. If you need faster propagation (e.g. an emergency key revocation), delete the affected
jwks:*keys (or callSecurityCache.clear()). - Negative caching of stable JWKS retrieval failures. When a JWKS fetch fails with a client error (4xx, e.g. a 404 for an unknown zone id), the failure is cached for 30 seconds: within that window, further requests for the same key fail fast with an exception instead of each making a network call — a stream of invalid tokens cannot keep hammering the identity service. Transient failures (5xx, timeouts, transport errors) are not cached, so they retry on the next request. After the entry expires, the next request retries the retrieval. Adapters that do not support per-entry TTLs (e.g.
SpringCacheSecurityCache) keep the negative entry for the store's own TTL instead.
- Waiters block during a refresh. Single-flight deduplicates the upstream call, but concurrent callers for the same key wait for that call to complete (bounded by the HTTP client's 30 s socket timeout). If the identity service responds slowly or hangs, validations for that key are delayed accordingly. A non-blocking variant — serving the last cached entry while refreshing in the background ("stale-while-revalidate") — is being evaluated as a follow-up; it involves a security trade-off (a revoked key would keep validating during an identity-service outage) that we want to align first.
SpringCacheSecurityCachehas no per-entry TTL. The store's own expiration policy applies to every entry, including the 30 s negative entries. Configure the store TTL accordingly (see the Warnings above).- The OIDC discovery cache has no negative caching. A failing discovery call is retried on the next miss; this is accepted because discovery is fetched at client setup, not on the per-request validation path.
Per-module details: token-client/README.md, java-security/README.md, spring-security-3/README.md.
The SAP Cloud Security Services Integration is published to maven central: https://search.maven.org/search?q=com.sap.cloud.security and is available as a Maven dependency. Add the following BOM to your dependency management in your pom.xml:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.sap.cloud.security</groupId>
<artifactId>java-bom</artifactId>
<version>4.2.0</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>along with libraries that you intend to use e.g. java-security
<dependencies>
<dependency>
<groupId>com.sap.cloud.security</groupId>
<artifactId>java-security</artifactId>
</dependency>
</dependencies>💡 Please refer to the Maven Dependencies section in the README.md of the library you intend to use for detailed information on which dependencies need to be added to the pom.xml.
Upgrading from version 3.x? See our comprehensive Migration Guide to 4.x for:
- Spring Boot 4.x migration - Step-by-step upgrade guide
- Spring Boot 3.x compatibility - Using legacy modules for gradual migration
- Removed modules - Migration paths for deprecated modules
- HTTP client changes - Switching from Apache HttpClient to Java 11 HttpClient
- Troubleshooting - Common issues and solutions
Quick Links:
If you intend to extend this library you can clone this repository and install this project with mvn as follows:
git clone https://github.com/SAP/cloud-security-services-integration-library
cd cloud-security-services-integration-library
mvn clean installPlease refer to each library's Troubleshooting section
| Link to troubleshooting section |
|---|
| spring-security |
| spring-xsuaa |
| java-security |
| token-client |
Most common reason for these errors are out of sync client library versions. All the modules of the Security Client libraries
should be always in the same version.
This can be verified by executing mvn dependency:tree command.
The easiest way to manage the module versions and keep them in sync is to use the BOM
The usage of the Security Client Libraries BOM is demonstrated also in the spring-security-hybrid-usage sample
The reference-instance plan is not an original plan of the Xsuaa service, therefore it is not supported by the Security Client Libraries out of the box.
For a workaround please refer to the #1279 (comment)
We welcome contributions to this project. Please see the CONTRIBUTING.md file for more details on how to contribute.
Support is no longer provided via the Issues feature in this Github repository.
Please use SAP official support channels to get support under component BC-CP-CF-SEC-LIB or Security Client Libraries.
Before opening support tickets, please check the Troubleshooting and Common Pitfalls sections first in addition to the READMEs of the modules that you are using from this repository.
Make sure to include the following mandatory information to get a response:
- List of module(s) of this library used by your application (java-security, spring-security, spring-xsuaa etc...) and version of this library installed in your application.
Alternative: maven dependency tree - Auth service set-up of your application (XSUAA, IAS, XSUAA+IAS, IAS+AMS, etc.)
- For exceptions: Stack trace that includes the executed code locations of this library that lead to the exception
- For unexpected 401 / 403 response codes: relevant log output of this library with active DEBUG flag (see module READMEs for a guide how to enable it)
- Steps you have tried to fix the problem
- Reason why you believe a bug in this library is causing your problem
Unfortunately, we can NOT offer consulting via support channels.
Please see our LICENSE for copyright and license information. Detailed information including third-party components and their licensing/copyright information is available in the REUSE tool.
