docs
Thread-local and reactive context management for associating security information with the current execution context. Includes the SecurityContext interface, SecurityContextHolder, strategies for different threading models, and reactive support.
Core Capabilities:
SecurityContext stores current Authentication objectSecurityContextHolder provides static access to thread-local security contextReactiveSecurityContextHolder using Project Reactor's ContextKey Interfaces and Classes:
SecurityContext - Interface storing Authentication (get/set methods)SecurityContextImpl - Default implementationTransientSecurityContext - Context that should not be persisted (e.g., not saved in session)SecurityContextHolder - Static utility for accessing context (uses strategy pattern)SecurityContextHolderStrategy - Strategy interface for context storageThreadLocalSecurityContextHolderStrategy - Default strategy (thread-bound)InheritableThreadLocalSecurityContextHolderStrategy - Inherited by child threadsGlobalSecurityContextHolderStrategy - Single global context (rarely used)ListeningSecurityContextHolderStrategy - Publishes context change eventsReactiveSecurityContextHolder - Reactive context holder using Reactor ContextDefault Behaviors:
MODE_THREADLOCAL (ThreadLocal-based, thread-bound)getContext() creates empty context if none exists (never returns null)setContext(null) effectively clears context (but use clearContext() instead)createEmptyContext() creates new SecurityContextImpl instancespring.security.strategyContext, accessed via getContext() returning Mono<SecurityContext>Threading Model:
Context, propagates through reactive chains automaticallyDelegatingSecurityContextExecutor, etc.) for async operationsLifecycle:
getContext()clearContext() callSupplier<SecurityContext> (since 5.8)Exceptions:
SecurityContextChangedEvent published by listening strategyisCleared(), wasAuthenticated(), isAuthenticated()Edge Cases:
getAuthentication() returns null if not authenticated (valid state)getContext() never returns null, but may return context with null authenticationReactiveSecurityContextHolderContextSnapshot for capturing context across thread boundaries (Project Reactor)Interface for storing the current Authentication object.
package org.springframework.security.core.context;
interface SecurityContext extends Serializable {
Authentication getAuthentication();
void setAuthentication(Authentication authentication);
}Method Details:
getAuthentication() - Returns the currently authenticated principal, or null if not authenticated.setAuthentication(Authentication) - Changes the currently authenticated principal, or removes authentication if null.Default implementation of SecurityContext.
package org.springframework.security.core.context;
class SecurityContextImpl implements SecurityContext {
SecurityContextImpl();
SecurityContextImpl(Authentication authentication);
Authentication getAuthentication();
void setAuthentication(Authentication authentication);
boolean equals(Object obj);
int hashCode();
String toString();
}Constructor Details:
Usage Example:
// Create security context
SecurityContext context = new SecurityContextImpl();
Authentication auth = UsernamePasswordAuthenticationToken.authenticated(
"user", null, AuthorityUtils.createAuthorityList("ROLE_USER"));
context.setAuthentication(auth);
// Or with constructor
SecurityContext context = new SecurityContextImpl(auth);Security context that should not be persisted (e.g., not saved in session).
package org.springframework.security.core.context;
class TransientSecurityContext implements SecurityContext {
TransientSecurityContext();
TransientSecurityContext(Authentication authentication);
Authentication getAuthentication();
void setAuthentication(Authentication authentication);
boolean equals(Object obj);
int hashCode();
String toString();
}Usage Example:
// For temporary authentication that shouldn't be persisted
Authentication tempAuth = // ... temporary authentication
SecurityContext transientContext = new TransientSecurityContext(tempAuth);
// This context will not be saved to HTTP session
SecurityContextHolder.setContext(transientContext);Thread-local storage for SecurityContext providing convenient static access.
package org.springframework.security.core.context;
class SecurityContextHolder {
static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";
static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";
static final String MODE_GLOBAL = "MODE_GLOBAL";
static final String SYSTEM_PROPERTY = "spring.security.strategy";
static void clearContext();
static SecurityContext getContext();
static void setContext(SecurityContext context);
static SecurityContext createEmptyContext();
static void setStrategyName(String strategyName);
static void setContextHolderStrategy(SecurityContextHolderStrategy strategy);
static SecurityContextHolderStrategy getContextHolderStrategy();
static Supplier<SecurityContext> getDeferredContext();
static String toString();
}Constants:
MODE_THREADLOCAL - Uses ThreadLocal (default, thread-bound).MODE_INHERITABLETHREADLOCAL - Uses InheritableThreadLocal (inherited by child threads).MODE_GLOBAL - Single global context (rarely used).SYSTEM_PROPERTY - System property name for setting strategy: "spring.security.strategy".Method Details:
clearContext() - Clears the current security context.getContext() - Returns the current context (creates empty one if none exists).setContext(SecurityContext) - Sets the current context.createEmptyContext() - Creates a new empty context instance.setStrategyName(String) - Sets the strategy by name (MODE_THREADLOCAL, etc.).setContextHolderStrategy(SecurityContextHolderStrategy) - Sets custom strategy.getContextHolderStrategy() - Returns the current strategy.getDeferredContext() - Gets deferred context supplier for lazy loading.Usage Example:
// Get current authentication (null-safe)
SecurityContext context = SecurityContextHolder.getContext();
Authentication auth = context != null ? context.getAuthentication() : null;
if (auth != null && auth.isAuthenticated()) {
String username = auth.getName();
Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();
Object principal = auth.getPrincipal(); // UserDetails or String
} else {
// No authentication - anonymous or unauthenticated
log.debug("No authenticated user in security context");
}
// Set authentication
Authentication newAuth = UsernamePasswordAuthenticationToken.authenticated(
"user", null, AuthorityUtils.createAuthorityList("ROLE_USER"));
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(newAuth);
SecurityContextHolder.setContext(context);
// Clear context (e.g., on logout)
SecurityContextHolder.clearContext();
// Check if user has role
boolean hasAdminRole = SecurityContextHolder.getContext()
.getAuthentication()
.getAuthorities().stream()
.anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"));
// Change strategy (before application initialization)
SecurityContextHolder.setStrategyName(
SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);Strategy interface for storing SecurityContext.
package org.springframework.security.core.context;
interface SecurityContextHolderStrategy {
void clearContext();
SecurityContext getContext();
void setContext(SecurityContext context);
SecurityContext createEmptyContext();
default Supplier<SecurityContext> getDeferredContext();
default void setDeferredContext(Supplier<SecurityContext> deferredContext);
}Method Details:
clearContext() - Clears the stored context.getContext() - Obtains the current context.setContext(SecurityContext) - Sets a new context.createEmptyContext() - Creates new empty context (should not use SecurityContextImpl directly to allow subclassing).getDeferredContext() - Gets supplier for deferred context loading (since 5.8).setDeferredContext(Supplier) - Sets deferred context (since 5.8).Default strategy using ThreadLocal.
package org.springframework.security.core.context;
class ThreadLocalSecurityContextHolderStrategy
implements SecurityContextHolderStrategy {
ThreadLocalSecurityContextHolderStrategy();
void clearContext();
SecurityContext getContext();
void setContext(SecurityContext context);
SecurityContext createEmptyContext();
Supplier<SecurityContext> getDeferredContext();
void setDeferredContext(Supplier<SecurityContext> deferredContext);
}Usage Example:
// Each thread has its own security context
SecurityContextHolderStrategy strategy =
new ThreadLocalSecurityContextHolderStrategy();
// In thread 1
strategy.setContext(context1);
// In thread 2
strategy.setContext(context2);
// Each thread sees its own context
SecurityContext retrieved = strategy.getContext(); // context for current threadStrategy that allows child threads to inherit parent's context.
package org.springframework.security.core.context;
class InheritableThreadLocalSecurityContextHolderStrategy
implements SecurityContextHolderStrategy {
InheritableThreadLocalSecurityContextHolderStrategy();
void clearContext();
SecurityContext getContext();
void setContext(SecurityContext context);
SecurityContext createEmptyContext();
}Usage Example:
SecurityContextHolderStrategy strategy =
new InheritableThreadLocalSecurityContextHolderStrategy();
SecurityContextHolder.setContextHolderStrategy(strategy);
// Set context in parent thread
Authentication auth = // ... authentication
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(auth);
SecurityContextHolder.setContext(context);
// Child threads inherit the context
new Thread(() -> {
// This thread automatically has the parent's security context
Authentication inherited =
SecurityContextHolder.getContext().getAuthentication();
System.out.println("Inherited user: " + inherited.getName());
}).start();Strategy using single static instance shared across all threads.
package org.springframework.security.core.context;
class GlobalSecurityContextHolderStrategy
implements SecurityContextHolderStrategy {
GlobalSecurityContextHolderStrategy();
void clearContext();
SecurityContext getContext();
void setContext(SecurityContext context);
SecurityContext createEmptyContext();
}Usage Example:
// Rarely used - all threads share same context
SecurityContextHolderStrategy strategy = new GlobalSecurityContextHolderStrategy();
SecurityContextHolder.setContextHolderStrategy(strategy);
// All threads see the same context (not thread-safe for modifications)Strategy that publishes context change events.
package org.springframework.security.core.context;
class ListeningSecurityContextHolderStrategy
implements SecurityContextHolderStrategy {
ListeningSecurityContextHolderStrategy(
SecurityContextHolderStrategy delegate,
SecurityContextChangedListener... listeners);
ListeningSecurityContextHolderStrategy(
SecurityContextHolderStrategy delegate,
Collection<SecurityContextChangedListener> listeners);
void clearContext();
SecurityContext getContext();
void setContext(SecurityContext context);
SecurityContext createEmptyContext();
Supplier<SecurityContext> getDeferredContext();
void setDeferredContext(Supplier<SecurityContext> deferredContext);
}Constructor Details:
Usage Example:
// Create custom listener
SecurityContextChangedListener listener = event -> {
log.info("Security context changed from {} to {}",
event.getOldContext(), event.getNewContext());
};
// Create listening strategy
SecurityContextHolderStrategy delegate =
new ThreadLocalSecurityContextHolderStrategy();
ListeningSecurityContextHolderStrategy strategy =
new ListeningSecurityContextHolderStrategy(delegate, listener);
SecurityContextHolder.setContextHolderStrategy(strategy);
// Now all context changes will trigger listener
SecurityContextHolder.setContext(newContext);Listener for security context changes.
package org.springframework.security.core.context;
interface SecurityContextChangedListener {
void securityContextChanged(SecurityContextChangedEvent event);
}Event published when security context changes.
package org.springframework.security.core.context;
class SecurityContextChangedEvent {
SecurityContextChangedEvent(SecurityContext oldContext,
SecurityContext newContext);
SecurityContext getOldContext();
SecurityContext getNewContext();
boolean isCleared();
boolean wasAuthenticated();
boolean isAuthenticated();
}Method Details:
getOldContext() - Returns the previous context.getNewContext() - Returns the new context.isCleared() - Returns true if context was cleared.wasAuthenticated() - Returns true if old context had authentication.isAuthenticated() - Returns true if new context has authentication.Usage Example:
SecurityContextChangedListener listener = event -> {
if (event.isCleared()) {
log.info("Context cleared");
} else if (!event.wasAuthenticated() && event.isAuthenticated()) {
log.info("User logged in");
} else if (event.wasAuthenticated() && !event.isAuthenticated()) {
log.info("User logged out");
} else {
log.info("Context changed");
}
};Publishes Micrometer observations for context changes.
package org.springframework.security.core.context;
class ObservationSecurityContextChangedListener
implements SecurityContextChangedListener {
ObservationSecurityContextChangedListener(ObservationRegistry registry);
void securityContextChanged(SecurityContextChangedEvent event);
}Usage Example:
ObservationRegistry observationRegistry = ObservationRegistry.create();
ObservationSecurityContextChangedListener listener =
new ObservationSecurityContextChangedListener(observationRegistry);
SecurityContextHolderStrategy strategy =
new ListeningSecurityContextHolderStrategy(
new ThreadLocalSecurityContextHolderStrategy(),
listener
);
SecurityContextHolder.setContextHolderStrategy(strategy);
// All context changes now create observationsSupplier for lazy loading of security context.
package org.springframework.security.core.context;
interface DeferredSecurityContext extends Supplier<SecurityContext> {
SecurityContext get();
boolean isGenerated();
}Method Details:
get() - Returns the security context, loading it if necessary.isGenerated() - Returns true if get() would generate a new context rather than load existing one.Usage Example:
// Used internally by Spring Security for lazy context loading
DeferredSecurityContext deferredContext = () -> {
// Load from session, database, etc.
return loadSecurityContextFromStorage();
};
SecurityContextHolder.getContextHolderStrategy()
.setDeferredContext(deferredContext);
// Context loaded on first access
SecurityContext context = SecurityContextHolder.getContext();Reactive context holder using Project Reactor's Context.
package org.springframework.security.core.context;
class ReactiveSecurityContextHolder {
static final Class<?> SECURITY_CONTEXT_KEY = SecurityContext.class;
static Mono<SecurityContext> getContext();
static Function<Context, Context> clearContext();
static Context withSecurityContext(Mono<? extends SecurityContext> securityContext);
static Context withAuthentication(Authentication authentication);
}Method Details:
getContext() - Returns Mono emitting current security context from reactive context.clearContext() - Returns function that clears security context from reactive context.withSecurityContext(Mono) - Creates reactive context with security context.withAuthentication(Authentication) - Creates reactive context with authentication.Usage Example:
// Get current authentication reactively
Mono<String> username = ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.map(Authentication::getName);
// Set authentication in reactive chain
Authentication auth = UsernamePasswordAuthenticationToken.authenticated(
"user", null, AuthorityUtils.createAuthorityList("ROLE_USER"));
Mono<String> result = Mono.just("data")
.contextWrite(ReactiveSecurityContextHolder.withAuthentication(auth));
// Or with security context
Mono<SecurityContext> contextMono = Mono.just(securityContext);
Mono<String> result = service.getData()
.contextWrite(ReactiveSecurityContextHolder.withSecurityContext(contextMono));
// Clear context
Mono<Void> cleared = Mono.empty()
.contextWrite(ReactiveSecurityContextHolder.clearContext());Integrates SecurityContextHolder with Project Reactor's ContextSnapshot.
package org.springframework.security.core.context;
class SecurityContextHolderThreadLocalAccessor
implements ThreadLocalAccessor<SecurityContext> {
static final String KEY = SecurityContext.class.getName();
SecurityContextHolderThreadLocalAccessor();
Object key();
SecurityContext getValue();
void setValue(SecurityContext value);
void reset();
}Usage Example:
// Automatically bridges ThreadLocal and reactive Context
// Usually configured via AutoConfiguration
// Manual configuration if needed
ContextRegistry.getInstance()
.registerThreadLocalAccessor(new SecurityContextHolderThreadLocalAccessor());Accessor for reactive security context holder.
package org.springframework.security.core.context;
class ReactiveSecurityContextHolderThreadLocalAccessor
implements ThreadLocalAccessor<SecurityContext> {
ReactiveSecurityContextHolderThreadLocalAccessor();
Object key();
SecurityContext getValue();
void setValue(SecurityContext value);
void reset();
}Executor that propagates SecurityContext to executed tasks.
package org.springframework.security.concurrent;
class DelegatingSecurityContextExecutor implements Executor {
DelegatingSecurityContextExecutor(Executor delegate);
DelegatingSecurityContextExecutor(Executor delegate,
SecurityContext securityContext);
void execute(Runnable task);
protected Runnable createRunnable(Runnable originalRunnable);
}Constructor Details:
Usage Example:
// Wrap existing executor
ExecutorService executorService = Executors.newFixedThreadPool(10);
Executor secureExecutor = new DelegatingSecurityContextExecutor(executorService);
// Tasks execute with current security context
secureExecutor.execute(() -> {
// This code has access to the security context from submitting thread
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
log.info("Task running as: {}", auth.getName());
});ExecutorService wrapper with security context propagation.
package org.springframework.security.concurrent;
class DelegatingSecurityContextExecutorService
extends DelegatingSecurityContextExecutor implements ExecutorService {
DelegatingSecurityContextExecutorService(ExecutorService delegate);
DelegatingSecurityContextExecutorService(ExecutorService delegate,
SecurityContext securityContext);
void execute(Runnable command);
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit) throws InterruptedException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
void shutdown();
List<Runnable> shutdownNow();
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
}Usage Example:
ExecutorService delegate = Executors.newCachedThreadPool();
ExecutorService secureExecutor =
new DelegatingSecurityContextExecutorService(delegate);
// Submit callable with context propagation
Future<String> future = secureExecutor.submit(() -> {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
return "Result for user: " + auth.getName();
});
String result = future.get();
log.info(result);
// Shutdown
secureExecutor.shutdown();
secureExecutor.awaitTermination(10, TimeUnit.SECONDS);Wraps Runnable to propagate security context.
package org.springframework.security.concurrent;
class DelegatingSecurityContextRunnable implements Runnable {
DelegatingSecurityContextRunnable(Runnable delegate);
DelegatingSecurityContextRunnable(Runnable delegate,
SecurityContext securityContext);
void run();
}Usage Example:
Runnable task = () -> {
log.info("Task logic");
};
// Wrap with current security context
Runnable secureTask = new DelegatingSecurityContextRunnable(task);
// Execute in new thread - context is propagated
new Thread(secureTask).start();Wraps Callable to propagate security context.
package org.springframework.security.concurrent;
class DelegatingSecurityContextCallable<V> implements Callable<V> {
DelegatingSecurityContextCallable(Callable<V> delegate);
DelegatingSecurityContextCallable(Callable<V> delegate,
SecurityContext securityContext);
V call() throws Exception;
}Usage Example:
Callable<String> task = () -> {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
return "Result from " + auth.getName();
};
// Wrap with current security context
Callable<String> secureTask = new DelegatingSecurityContextCallable<>(task);
// Submit to executor
Future<String> future = executorService.submit(secureTask);
String result = future.get();Thread-Local Usage:
// Always clear context when done (especially in pooled threads)
try {
SecurityContextHolder.setContext(context);
// ... perform operations
} finally {
SecurityContextHolder.clearContext();
}Async Execution:
// Use delegating executors for automatic context propagation
Executor executor = new DelegatingSecurityContextExecutor(
Executors.newCachedThreadPool());
executor.execute(() -> {
// Context automatically available
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
});Reactive Programming:
// Use withAuthentication or withSecurityContext
return webClient.get()
.uri("/api/data")
.retrieve()
.bodyToMono(String.class)
.contextWrite(ReactiveSecurityContextHolder.withAuthentication(auth));Testing:
@BeforeEach
void setup() {
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication auth = new TestingAuthenticationToken("user", "pass", "ROLE_USER");
context.setAuthentication(auth);
SecurityContextHolder.setContext(context);
}
@AfterEach
void cleanup() {
SecurityContextHolder.clearContext();
}