Class SecurityContext

java.lang.Object
com.sap.cloud.security.token.SecurityContext

public class SecurityContext extends Object
Thread-local storage for security-related context information.

This class provides static methods to access and manage security tokens, certificates, and service plans on a per-thread basis. Each thread maintains its own isolated context, making this class safe for use in multi-threaded environments such as web servers handling concurrent requests.

Core Features:

  • Token Management: Store and retrieve access tokens, ID tokens, and XSUAA tokens
  • Automatic Token Resolution: Lazy-load tokens via registered extensions when needed
  • Token Caching: Cache resolved tokens
  • Certificate Storage: Store X.509 client certificates for mTLS scenarios
  • Service Plan Tracking: Store Identity Authentication Service plan information

Thread-Local Storage: All security context data is stored in a ThreadLocal instance, ensuring thread isolation. Each thread gets its own SecurityContext containing all security-related state. This design:

  • Eliminates the need for multiple ThreadLocal fields (reduces memory overhead)
  • Improves cache locality by grouping related data in one object
  • Simplifies cleanup with clear() removing all data in one call

Token Extension System: The class supports pluggable token resolution via extensions:

Extensions are registered globally but operate on thread-local context. Resolved tokens are automatically cached until expiration.

Usage Example:

// 1. Register extensions (once at application startup)
SecurityContext.registerIdTokenExtension(new DefaultIdTokenExtension(tokenService, iasConfig));
SecurityContext.registerXsuaaTokenExtension(new DefaultXsuaaTokenExtension(tokenService, xsuaaConfig));

// 2. Set the initial token (e.g., in authentication filter)
Token accessToken = parseFromAuthorizationHeader(request);
SecurityContext.setToken(accessToken);

// 3. Access tokens in business logic
Token idToken = SecurityContext.getIdToken();  // Automatically resolved if not cached
Token xsuaaToken = SecurityContext.getXsuaaToken();  // Automatically exchanged if needed

// 4. Clean up after request (e.g., in filter's finally block)
SecurityContext.clear();  // Clears all tokens, certificate, and service plans

Token Lifecycle:

  1. Initial Token: Set via setToken(Token) (typically from HTTP Authorization header)
  2. Caching: getIdToken() and getXsuaaToken() cache resolved tokens
  3. Re-resolution: Expired tokens trigger automatic re-resolution via extensions
  4. Cleanup: clear() or clearContext() removes all tokens

Memory Management: When using thread pools (e.g., servlet containers), always call clear() or clearContext() at the end of request processing to prevent memory leaks. Thread pools reuse threads, so stale context data can accumulate if not cleaned up properly.

Cross-Thread Usage: For advanced scenarios like asynchronous token exchange, you can capture the context and pass it to other threads:

SecurityContext ctx = SecurityContext.get();
CompletableFuture.supplyAsync(() -> {
    Token exchangedToken = tokenExchange.exchange(ctx.token);
    ctx.updateToken(exchangedToken);  // Preserves other context properties
    return callApi(exchangedToken);
});
See Also:
  • Method Details

    • getToken

      @Nullable public static Token getToken()
      Returns the current access token for this thread.

      This returns the token set via setToken(Token) or replaced via updateToken(Token). In hybrid IAS/XSUAA scenarios, this may be either:

      • IAS token (from the HTTP Authorization header)
      • XSUAA token (if exchanged via updateToken(Token))

      To get the original token before any exchanges, use getInitialToken().

      Returns:
      the current access token, or null if none is set
      See Also:
    • setToken

      public static void setToken(Token token)
      Sets the access token for the current thread and resets the security context.

      This method:

      1. Sets token to the provided value
      2. Saves a copy in initialToken (preserved even if token is later replaced)
      3. Clears cached derived tokens (idToken, xsuaaToken)

      Why Derived Tokens Are Cleared: ID tokens and XSUAA tokens are derived from the access token. When the access token changes, cached derived tokens become invalid and must be re-resolved.

      What Is NOT Cleared:

      • Client certificate — Not token-specific, may persist across token rotations
      • Service plans — Part of the request context, not tied to the token
      • Extensions — Global configuration, not thread-specific

      Usage:

      // In authentication filter
      Token accessToken = parseAuthorizationHeader(request);
      SecurityContext.setToken(accessToken);
      
      Parameters:
      token - the access token to set (typically from the HTTP Authorization header)
    • updateToken

      public static void updateToken(Token token)
      Updates only the token field without clearing derived tokens or resetting context.

      For internal usage only. Use setToken(Token) for normal authentication scenarios. This method is intended for advanced scenarios like cross-thread token exchange where you want to preserve other context properties.

      Difference from setToken(Token):

      Method Comparison
      Action setToken(Token) updateToken(Token)
      Updates token
      Updates initialToken
      Clears idToken
      Clears xsuaaToken

      Use Case: Cross-Thread Token Exchange

      SecurityContext ctx = SecurityContext.get();
      CompletableFuture.supplyAsync(() -> {
          Token exchangedToken = tokenExchange.exchange(ctx.token);
          ctx.updateToken(exchangedToken);  // Preserves idToken, extensions, etc.
          return callApi(exchangedToken);
      });
      
      Parameters:
      token - the token to set
    • getInitialToken

      @Nullable public static Token getInitialToken()
      Returns the initial access token for the current thread.

      This returns the original token set via setToken(Token), even if token was later replaced via updateToken(Token). Use this to access the original authentication token after token exchanges.

      Example: Hybrid IAS/XSUAA Scenario

      // 1. IAS token from HTTP Authorization header
      SecurityContext.setToken(iasToken);
      
      // 2. Exchange to XSUAA token
      Token xsuaaToken = tokenExchange.exchange(iasToken);
      SecurityContext.get().updateToken(xsuaaToken);
      
      // 3. Access tokens
      Token current = SecurityContext.getToken();         // → xsuaaToken
      Token original = SecurityContext.getInitialToken(); // → iasToken
      
      Returns:
      the initial access token, or null if none was set
      See Also:
    • getAccessToken

      @Nullable public static AccessToken getAccessToken()
      Returns the current access token as an AccessToken, if applicable.

      This is a convenience method that performs type checking and casting. It returns null if the current token is not an AccessToken instance (e.g., if it's an ID token or custom token implementation).

      Usage:

      AccessToken accessToken = SecurityContext.getAccessToken();
      if (accessToken != null) {
          String clientId = accessToken.getClientId();
          List<String> scopes = accessToken.getScopes();
      }
      
      Returns:
      the current token as AccessToken, or null if:
    • clearToken

      public static void clearToken()
      Removes the current access token from the security context.

      This clears token but does NOT clear:

      • initialToken, idToken, xsuaaToken — Use clear() to remove all tokens

      Subsequent calls to getToken() will return null until a new token is set.

    • getIdToken

      @Nullable public static Token getIdToken()
      Retrieves the ID token associated with the current context.

      This method implements lazy-loading and caching:

      1. Returns cached ID token if it exists and is valid
      2. If IdTokenExtension is registered, calls IdTokenExtension.resolveIdToken(Token)
      3. Caches the resolved token for subsequent calls
      4. Returns the resolved token (or null if resolution failed)

      Extension Requirement: Automatic resolution requires registering an IdTokenExtension via registerIdTokenExtension(IdTokenExtension). Without an extension, this method only returns previously cached tokens.

      Returns:
      the ID token, or null if:
      • No extension is registered
      • Token resolution fails (network error, invalid token, etc.)
      • No access token is available for token exchange
      See Also:
    • setXsuaaToken

      public static void setXsuaaToken(Token token)
      Sets the XSUAA token for the current thread.
      Parameters:
      token - the XSUAA token to set
    • getXsuaaToken

      @Nullable public static Token getXsuaaToken()
      Retrieves the XSUAA token associated with the current context.

      This method implements lazy-loading, caching, and automatic token exchange:

      1. Returns cached XSUAA token if it exists and is valid
      2. If XsuaaTokenExtension is registered, calls XsuaaTokenExtension.resolveXsuaaToken(Token)
      3. Caches the resolved/exchanged token for subsequent calls
      4. Returns the resolved token (or null if resolution failed)

      Hybrid Authentication (Level 0 Migration): In hybrid scenarios where IAS tokens are received but XSUAA authorization is still used, this method automatically exchanges IAS tokens to XSUAA format via the registered XsuaaTokenExtension. The exchanged token is cached to avoid repeated exchanges.

      Extension Requirement: Automatic exchange requires:

      Without an extension, this method only returns previously cached XSUAA tokens.
      Returns:
      the XSUAA token, or null if:
      • No extension is registered
      • Token exchange fails (network error, invalid configuration, etc.)
      • No source token is available for exchange
      See Also:
    • getClientCertificate

      @Nullable public static Certificate getClientCertificate()
      Returns the X.509 client certificate for the current thread.

      This is used in mutual TLS (mTLS) scenarios where the client authenticates via certificate instead of (or in addition to) JWT tokens. The certificate is typically extracted from the HTTP request by the web server/servlet container.

      Usage: Certificate-Based Authentication

      Certificate cert = SecurityContext.getClientCertificate();
      if (cert != null) {
          String subjectDN = cert.getSubjectDN();
          // Perform certificate-based authorization
      }
      
      Returns:
      the client certificate, or null if:
      See Also:
    • setClientCertificate

      public static void setClientCertificate(Certificate certificate)
      Sets the X.509 client certificate for the current thread's security context.

      This is typically called by authentication filters/interceptors that extract the certificate from the HTTP request. In servlet-based applications, the certificate is usually available via the javax.servlet.request.X509Certificate request attribute.

      Example: Servlet Filter

      X509Certificate[] certs = (X509Certificate[])
          request.getAttribute("javax.servlet.request.X509Certificate");
      
      if (certs != null && certs.length > 0) {
          Certificate cert = Certificate.create(certs[0]);
          SecurityContext.setClientCertificate(cert);
      }
      
      Parameters:
      certificate - the client certificate to store (may be null to clear it)
      See Also:
    • getServicePlans

      @Nullable public static List<String> getServicePlans()
      Returns the Identity Authentication Service plans associated with the current request.

      Service plans indicate which IAS features/capabilities are available for the current user/tenant. This information is typically extracted from custom HTTP headers (e.g., X-Identity-Service-Plan) in multi-tenant scenarios.

      Example Plans:

      • "default" — Basic IAS features
      • "application" — Full IAS application features
      • "sso" — Single Sign-On enabled
      Returns:
      the list of service plans, or null if:
      See Also:
    • setServicePlans

      public static void setServicePlans(String servicePlansHeader)
      Parses and stores Identity Authentication Service plans from a comma-separated header value.

      Expected Format: "plan1", "plan2", "plan3"
      Plans are extracted by splitting on commas and removing surrounding quotes.

      Example:

      // HTTP header: X-Identity-Service-Plan: "default", "application", "sso"
      String header = request.getHeader("X-Identity-Service-Plan");
      SecurityContext.setServicePlans(header);
      
      List<String> plans = SecurityContext.getServicePlans();
      // → ["default", "application", "sso"]
      
      Parameters:
      servicePlansHeader - the comma-separated service plans header value (must not be null)
      Throws:
      NullPointerException - if servicePlansHeader is null
      See Also:
    • clearServicePlans

      public static void clearServicePlans()
      Removes the service plans from the security context.

      Subsequent calls to getServicePlans() will return null until new plans are set.

    • registerIdTokenExtension

      public static void registerIdTokenExtension(IdTokenExtension ext)
      Registers a global IdTokenExtension for automatic ID token resolution.

      The extension is called by getIdToken(). Only one extension can be registered at a time; calling this method multiple times overwrites the previous extension.

      Registration Timing: Typically called once during application startup (e.g., in Spring Boot's @PostConstruct or ApplicationRunner). However, per-request registration is also supported for scenarios where dependency injection causes circular bean initialization issues. Extensions are stateless, so overwriting the previous extension is safe.

      Example: Spring Boot Configuration

      @Configuration
      public class SecurityConfig {
          @PostConstruct
          public void registerExtensions() {
              SecurityContext.registerIdTokenExtension(
                  new DefaultIdTokenExtension(tokenService, iasConfig)
              );
          }
      }
      
      Parameters:
      ext - the ID token extension to register (may be null to unregister)
      See Also:
    • registerXsuaaTokenExtension

      public static void registerXsuaaTokenExtension(XsuaaTokenExtension ext)
      Registers a global XsuaaTokenExtension for automatic XSUAA token resolution/exchange.

      The extension is called by getXsuaaToken(). Only one extension can be registered at a time; calling this method multiple times overwrites the previous extension.

      Registration Timing: Typically called once during application startup (e.g., in Spring Boot's @PostConstruct or ApplicationRunner). However, per-request registration is also supported for scenarios where dependency injection causes circular bean initialization issues. Extensions are stateless, so overwriting the previous extension is safe.

      Example: Spring Boot Configuration

      @Configuration
      public class SecurityConfig {
          @PostConstruct
          public void registerExtensions() {
              SecurityContext.registerXsuaaTokenExtension(
                  new DefaultXsuaaTokenExtension(tokenService, xsuaaConfig)
              );
          }
      }
      
      Parameters:
      ext - the XSUAA token extension to register (may be null to unregister)
      See Also:
    • clear

      public static void clear()
      Clears all security context data for the current thread.

      This method removes:

      • Access token (token)
      • Initial token (initialToken)
      • Cached ID token (idToken)
      • Cached XSUAA token (xsuaaToken)
      • Client certificate (certificate)
      • Service plans (servicePlans)

      What Is NOT Cleared:

      • Registered extensions (idTokenExtension, xsuaaTokenExtension) — These are global and shared across all threads
      • The SecurityContext instance itself — Kept in ThreadLocal for potential reuse

      Usage: Request Cleanup

      try {
          SecurityContext.setToken(token);
          // ... process request ...
      } finally {
          SecurityContext.clear();  // Clean up thread-local state
      }
      

      Difference from clearContext():

      • clear() — Nulls out all fields but keeps the SecurityContext instance
      • clearContext() — Removes the entire SecurityContext from ThreadLocal
      See Also:
    • clearContext

      public static void clearContext()
      Removes the entire SecurityContext instance from thread-local storage.

      This performs complete ThreadLocal cleanup by removing the SecurityContext instance. Use this to prevent memory leaks in thread pool environments where threads are reused (e.g., servlet containers, application servers).

      Difference from clear():

      • clear() — Nulls out all fields but keeps the SecurityContext instance in ThreadLocal (faster for subsequent requests on the same thread)
      • clearContext() — Removes the entire instance from ThreadLocal (complete cleanup, prevents memory leaks)

      When to Use Each:

      Cleanup Method Comparison
      Scenario Recommended Method
      Servlet request cleanup clear()
      Thread pool shutdown clearContext()
      Test cleanup (after each test) clearContext()
      Custom thread lifecycle management clearContext()

      Best Practice: Request Handling

      try {
          SecurityContext.setToken(token);
          // ... process request ...
      } finally {
          SecurityContext.clearContext();  // Complete cleanup
      }
      
      See Also: