Skip to content

About

Integration libraries and samples for authenticating users and clients bound to XSUAA authentication and authorization service or Identity authentication service.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

176 stars

Watchers

10 watching

Forks

Latest commit

 

History

3,013 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

REUSE status Maven Build main Fosstars security rating CodeQL

SAP BTP Security Services Integration Libraries

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.

📣 What's New in 4.x

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-starter 4.x
  • Spring Boot 3.x users: Use new resourceserver-security-spring-boot-3-starter 4.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.

Table of Contents

  1. What's New in 4.x
  2. Prerequisites
  3. Usage
  4. Installation
  5. Migration Guide
  6. Troubleshooting
  7. Common Pitfalls
  8. Contributing
  9. How to get support
  10. License

Prerequisites

Before you can use the SAP Cloud Security Services Integration libraries, you must fulfil the following requirements:

  1. Knowledge of Java programming and (Optional) Spring Boot framework.
  2. Access to an SAP BTP account and the XSUAA or Identity service.
  3. Familiarity with OAuth 2.0 and JWT (JSON Web Tokens).
  4. Java 17
  5. Maven 3.9.0 or later
  6. (Optional) Spring Boot 4.x or later, Spring Security 7.x or later if using the Spring integration

❗ For Java 8 and 11 please use 2.x release of this library.

Usage

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:

  1. A user accesses the web application using a browser or mobile device which begins a new server session.
  2. 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.
  3. An access token is retrieved from the OAuth2 server in exchange for the authorization code.
  4. 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.

OAuth 2.0 Authorization code flow

OAuth2 resource servers (as the one in step 4) require libraries for validating access tokens.

2.1. Token Validation

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)

2.1.1. Jakarta EE web applications

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:

2.1.2. Spring Boot web applications

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.

Spring Boot Version Support
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.

Spring Boot 3.x Compatibility

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.

Migrating from spring-xsuaa

⚠️ Deprecated: The spring-xsuaa module has been removed in version 4.x.

If you're using the deprecated spring-xsuaa module:

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)

2.2. Token Flows for token retrievals

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.

2.3 Testing utilities

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

2.4 Token Exchange for Hybrid Authentication

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).

Important Constraints

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.

How Token Exchange Works

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.

Token Exchange Modes

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_XSUAA when 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_XSUAA for maximum backward compatibility—the app operates based on XSUAA tokens like before
  • Use DISABLED or remove the property completely after completing the migration to IAS

Prerequisites for Token Exchange

  1. Both XSUAA and IAS service bindings must be configured
  2. IAS service binding must include xsuaa-cross-consumption: true parameter
  3. 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.

Troubleshooting

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

2.5 Distributed Caching (since 4.2.0)

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.

Recommendation: when do I need a distributed cache?

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.

How the caches behave

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 set carries 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
    CaffeineSecurityCache honored — shorter TTLs (negative entries) expire independently of the cache-wide default
    SpringCacheSecurityCache delegated to the store's own expiration policy (e.g. Redis entryTtl)
    custom adapters yours to implement; ttl == null means "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 exp claim (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.

The SPI and shipped adapters

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 (in token-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 (in spring-security / spring-security-3) — wraps a Spring org.springframework.cache.Cache so any Spring-managed backend (Redis, Hazelcast, Caffeine, ...) works via Spring's CacheManager abstraction.

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.

Enabling on Spring Boot

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  # default
import 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.

Enabling on plain Java

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();

Configuration reference

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.

Cache keys and entry content

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.

Bring your own cache

The SPI has four methods: get, set, delete, clear. Two invariants matter:

  1. 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.
  2. Thread-safe. The library calls the cache from many threads concurrently.
Snippet 1 — Redis via Jedis (plain Java, no Spring)
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.

Snippet 2 — Any Spring-managed backend (Spring Boot)

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
}

Verification and troubleshooting

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:*, and oidc:* 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)

Security considerations

  • 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 call SecurityCache.clear().

Warnings

  • 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 shipped SpringCacheSecurityCache (and any adapter that delegates to infrastructure-managed expiration) lets the store's own policy decide. If your store has no expiration (e.g. a RedisCacheManager without entryTtl, or a plain SET without EXPIRE), 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 with Key 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's exp claim 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 call SecurityCache.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.

Known limitations

  • 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.
  • SpringCacheSecurityCache has 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.

Installation

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.

Migration Guide

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 install

Troubleshooting

Please refer to each library's Troubleshooting section

Link to troubleshooting section
spring-security
spring-xsuaa
java-security
token-client

Common Pitfalls

java.lang.NoSuchMethodError and java.lang.ClassNotFoundException errors

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

reference-instance plan not supported

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)

Contributing

We welcome contributions to this project. Please see the CONTRIBUTING.md file for more details on how to contribute.

How to get support

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.

License

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.

About

Integration libraries and samples for authenticating users and clients bound to XSUAA authentication and authorization service or Identity authentication service.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

176 stars

Watchers

10 watching

Forks

Releases

Packages

Used by

Contributors

Languages