docs
Spring Security Core provides comprehensive support for propagating security context across thread boundaries in concurrent and asynchronous execution scenarios. This ensures that security context is available in background threads, executor services, and scheduled tasks.
Core Capabilities:
DelegatingSecurityContextExecutor - Executor that propagates security contextDelegatingSecurityContextExecutorService - ExecutorService with context propagationDelegatingSecurityContextTaskExecutor - TaskExecutor with context propagationDelegatingSecurityContextAsyncTaskExecutor - AsyncTaskExecutor with context propagationDelegatingSecurityContextTaskScheduler - TaskScheduler with context propagationSecurityContextHolderStrategy - Strategy for storing security context (ThreadLocal, InheritableThreadLocal, Global, Listening)Key Interfaces and Classes:
DelegatingSecurityContextExecutor - Wraps Executor, propagates contextDelegatingSecurityContextExecutorService - Wraps ExecutorService, propagates contextDelegatingSecurityContextTaskExecutor - Wraps TaskExecutor, propagates contextDelegatingSecurityContextAsyncTaskExecutor - Wraps AsyncTaskExecutor, propagates contextDelegatingSecurityContextTaskScheduler - Wraps TaskScheduler, propagates contextSecurityContextHolderStrategy - Strategy interface for context storageThreadLocalSecurityContextHolderStrategy - ThreadLocal storage (default)InheritableThreadLocalSecurityContextHolderStrategy - InheritableThreadLocal storageGlobalSecurityContextHolderStrategy - Global (static) storageListeningSecurityContextHolderStrategy - Listener-based storageDefault Behaviors:
ThreadLocalSecurityContextHolderStrategyThreading Model:
Lifecycle:
Exceptions:
Edge Cases:
Wraps an Executor to propagate the current SecurityContext to threads that execute tasks.
package org.springframework.security.concurrent;
class DelegatingSecurityContextExecutor implements Executor {
DelegatingSecurityContextExecutor(Executor delegate);
DelegatingSecurityContextExecutor(
Executor delegate,
SecurityContext securityContext);
void execute(Runnable command);
}Constructors:
DelegatingSecurityContextExecutor(Executor delegate)DelegatingSecurityContextExecutor(
Executor delegate,
SecurityContext securityContext)Key Methods:
void execute(Runnable command)Example:
Executor executor = Executors.newFixedThreadPool(10);
DelegatingSecurityContextExecutor secureExecutor =
new DelegatingSecurityContextExecutor(executor);
// Current security context will be propagated
Authentication auth = new UsernamePasswordAuthenticationToken(
"user",
"password",
AuthorityUtils.createAuthorityList("ROLE_USER")
);
SecurityContextHolder.getContext().setAuthentication(auth);
// Submit task - security context will be available in the task
secureExecutor.execute(() -> {
// Security context is available here
Authentication currentAuth = SecurityContextHolder.getContext()
.getAuthentication();
// currentAuth == auth
System.out.println("Executing as: " + currentAuth.getName());
});With Specific Context:
Executor executor = Executors.newFixedThreadPool(10);
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication systemAuth = new UsernamePasswordAuthenticationToken(
"system",
null,
AuthorityUtils.createAuthorityList("ROLE_SYSTEM")
);
context.setAuthentication(systemAuth);
DelegatingSecurityContextExecutor systemExecutor =
new DelegatingSecurityContextExecutor(executor, context);
// Task executes with system context regardless of current context
systemExecutor.execute(() -> {
Authentication auth = SecurityContextHolder.getContext()
.getAuthentication();
// auth.getName() == "system"
});Wraps an ExecutorService to propagate security context, extending DelegatingSecurityContextExecutor.
package org.springframework.security.concurrent;
class DelegatingSecurityContextExecutorService
extends DelegatingSecurityContextExecutor
implements ExecutorService {
DelegatingSecurityContextExecutorService(ExecutorService delegate);
DelegatingSecurityContextExecutorService(
ExecutorService delegate,
SecurityContext securityContext);
// ExecutorService methods
void shutdown();
List<Runnable> shutdownNow();
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
<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;
}Constructors:
DelegatingSecurityContextExecutorService(ExecutorService delegate)DelegatingSecurityContextExecutorService(
ExecutorService delegate,
SecurityContext securityContext)Example:
ExecutorService executorService = Executors.newCachedThreadPool();
DelegatingSecurityContextExecutorService secureExecutorService =
new DelegatingSecurityContextExecutorService(executorService);
// Set current context
Authentication auth = ...;
SecurityContextHolder.getContext().setAuthentication(auth);
// Submit callable with context propagation
Future<String> future = secureExecutorService.submit(() -> {
Authentication currentAuth = SecurityContextHolder.getContext()
.getAuthentication();
return "Executed by: " + currentAuth.getName();
});
String result = future.get();
// result contains the authenticated user's name
// Submit multiple tasks
List<Callable<String>> tasks = Arrays.asList(
() -> {
Authentication a = SecurityContextHolder.getContext()
.getAuthentication();
return "Task 1: " + a.getName();
},
() -> {
Authentication a = SecurityContextHolder.getContext()
.getAuthentication();
return "Task 2: " + a.getName();
}
);
List<Future<String>> futures = secureExecutorService.invokeAll(tasks);
for (Future<String> f : futures) {
System.out.println(f.get());
}
// Cleanup
secureExecutorService.shutdown();Wraps a Spring TaskExecutor to propagate security context.
package org.springframework.security.concurrent;
class DelegatingSecurityContextTaskExecutor implements TaskExecutor {
DelegatingSecurityContextTaskExecutor(TaskExecutor delegate);
DelegatingSecurityContextTaskExecutor(
TaskExecutor delegate,
SecurityContext securityContext);
void execute(Runnable task);
}Constructors:
DelegatingSecurityContextTaskExecutor(TaskExecutor delegate)DelegatingSecurityContextTaskExecutor(
TaskExecutor delegate,
SecurityContext securityContext)Example:
@Configuration
public class AsyncConfiguration implements AsyncConfigurer {
@Override
@Bean
public TaskExecutor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(100);
executor.initialize();
// Wrap with security context propagation
return new DelegatingSecurityContextTaskExecutor(executor);
}
}
// Usage with @Async
@Service
public class AsyncService {
@Async
public void asyncMethod() {
// Security context is available here
Authentication auth = SecurityContextHolder.getContext()
.getAuthentication();
// Process with authenticated context
}
}Wraps a Spring AsyncTaskExecutor to propagate security context.
package org.springframework.security.concurrent;
class DelegatingSecurityContextAsyncTaskExecutor
extends DelegatingSecurityContextTaskExecutor
implements AsyncTaskExecutor {
DelegatingSecurityContextAsyncTaskExecutor(AsyncTaskExecutor delegate);
DelegatingSecurityContextAsyncTaskExecutor(
AsyncTaskExecutor delegate,
SecurityContext securityContext);
// AsyncTaskExecutor methods
void execute(Runnable task, long startTimeout);
Future<?> submit(Runnable task);
<T> Future<T> submit(Callable<T> task);
}Example:
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.initialize();
DelegatingSecurityContextAsyncTaskExecutor secureExecutor =
new DelegatingSecurityContextAsyncTaskExecutor(executor);
// Submit with timeout
secureExecutor.execute(() -> {
Authentication auth = SecurityContextHolder.getContext()
.getAuthentication();
// Process
}, 5000);
// Submit callable
Future<String> future = secureExecutor.submit(() -> {
Authentication auth = SecurityContextHolder.getContext()
.getAuthentication();
return "Result from: " + auth.getName();
});Wraps a Spring TaskScheduler to propagate security context to scheduled tasks.
package org.springframework.security.concurrent;
class DelegatingSecurityContextTaskScheduler implements TaskScheduler {
DelegatingSecurityContextTaskScheduler(TaskScheduler delegate);
DelegatingSecurityContextTaskScheduler(
TaskScheduler delegate,
SecurityContext securityContext);
// TaskScheduler methods
ScheduledFuture<?> schedule(Runnable task, Trigger trigger);
ScheduledFuture<?> schedule(Runnable task, Date startTime);
<T> ScheduledFuture<T> schedule(Callable<T> task, Date startTime);
ScheduledFuture<?> scheduleAtFixedRate(
Runnable task,
Date startTime,
long period);
ScheduledFuture<?> scheduleAtFixedRate(
Runnable task,
long period);
ScheduledFuture<?> scheduleWithFixedDelay(
Runnable task,
Date startTime,
long delay);
ScheduledFuture<?> scheduleWithFixedDelay(
Runnable task,
long delay);
}Example:
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(5);
scheduler.initialize();
DelegatingSecurityContextTaskScheduler secureScheduler =
new DelegatingSecurityContextTaskScheduler(scheduler);
// Schedule with fixed rate
secureScheduler.scheduleAtFixedRate(() -> {
Authentication auth = SecurityContextHolder.getContext()
.getAuthentication();
// Scheduled task with security context
System.out.println("Scheduled task by: " + auth.getName());
}, 1000);
// Schedule with delay
secureScheduler.scheduleWithFixedDelay(() -> {
Authentication auth = SecurityContextHolder.getContext()
.getAuthentication();
// Process
}, 5000);
// Schedule at specific time
Date startTime = new Date(System.currentTimeMillis() + 60000);
secureScheduler.schedule(() -> {
Authentication auth = SecurityContextHolder.getContext()
.getAuthentication();
// One-time scheduled task
}, startTime);Strategy interface for storing and retrieving SecurityContext.
package org.springframework.security.core.context;
interface SecurityContextHolderStrategy {
void clearContext();
SecurityContext getContext();
void setContext(SecurityContext context);
SecurityContext createEmptyContext();
}Key Methods:
void clearContext()SecurityContext getContext()void setContext(SecurityContext context)SecurityContext createEmptyContext()Default strategy using ThreadLocal storage. Each thread has its own security context.
package org.springframework.security.core.context;
class ThreadLocalSecurityContextHolderStrategy
implements SecurityContextHolderStrategy {
ThreadLocalSecurityContextHolderStrategy();
void clearContext();
SecurityContext getContext();
void setContext(SecurityContext context);
SecurityContext createEmptyContext();
}Behavior:
Example:
// This is the default - no explicit configuration needed
// But can be set explicitly:
SecurityContextHolder.setStrategyName(
SecurityContextHolder.MODE_THREADLOCAL
);
// Or:
SecurityContextHolder.setContextHolderStrategy(
new ThreadLocalSecurityContextHolderStrategy()
);Strategy using InheritableThreadLocal storage. Security context is inherited by child threads.
package org.springframework.security.core.context;
class InheritableThreadLocalSecurityContextHolderStrategy
implements SecurityContextHolderStrategy {
InheritableThreadLocalSecurityContextHolderStrategy();
void clearContext();
SecurityContext getContext();
void setContext(SecurityContext context);
SecurityContext createEmptyContext();
}Behavior:
Example:
// Set strategy
SecurityContextHolder.setStrategyName(
SecurityContextHolder.MODE_INHERITABLETHREADLOCAL
);
// Set context in parent thread
Authentication auth = ...;
SecurityContextHolder.getContext().setAuthentication(auth);
// Create child thread - context is inherited
Thread childThread = new Thread(() -> {
// Security context is available here
Authentication inheritedAuth = SecurityContextHolder.getContext()
.getAuthentication();
// inheritedAuth == auth
});
childThread.start();Note: When using executors, prefer DelegatingSecurityContextExecutor over inheritable thread local, as executors reuse threads and context inheritance can cause issues.
Strategy using a single global (static) security context 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();
}Behavior:
Example:
// Set strategy (NOT recommended for multi-threaded apps)
SecurityContextHolder.setStrategyName(
SecurityContextHolder.MODE_GLOBAL
);
// All threads share the same context
SecurityContextHolder.getContext().setAuthentication(auth);Strategy that notifies listeners when security context changes.
package org.springframework.security.core.context;
class ListeningSecurityContextHolderStrategy
implements SecurityContextHolderStrategy {
ListeningSecurityContextHolderStrategy(
SecurityContextHolderStrategy delegate);
void addListener(SecurityContextChangedListener listener);
void removeListener(SecurityContextChangedListener listener);
void clearContext();
SecurityContext getContext();
void setContext(SecurityContext context);
SecurityContext createEmptyContext();
}Constructor:
ListeningSecurityContextHolderStrategy(
SecurityContextHolderStrategy delegate)Key Methods:
void addListener(SecurityContextChangedListener listener)void removeListener(SecurityContextChangedListener listener)Example:
ThreadLocalSecurityContextHolderStrategy delegate =
new ThreadLocalSecurityContextHolderStrategy();
ListeningSecurityContextHolderStrategy strategy =
new ListeningSecurityContextHolderStrategy(delegate);
// Add listener
strategy.addListener((oldContext, newContext) -> {
System.out.println("Security context changed");
if (oldContext != null) {
System.out.println("Old: " + oldContext.getAuthentication());
}
if (newContext != null) {
System.out.println("New: " + newContext.getAuthentication());
}
});
SecurityContextHolder.setContextHolderStrategy(strategy);
// Context changes will trigger listener
Authentication auth = ...;
SecurityContextHolder.getContext().setAuthentication(auth);
// Listener is notifiedListener interface for security context changes.
package org.springframework.security.core.context;
interface SecurityContextChangedListener {
void securityContextChanged(
@Nullable SecurityContext oldContext,
@Nullable SecurityContext newContext);
}@Configuration
@EnableAsync
public class AsyncSecurityConfiguration implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("async-");
executor.initialize();
// Wrap with security context propagation
return new DelegatingSecurityContextTaskExecutor(executor);
}
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(5);
scheduler.setThreadNamePrefix("scheduled-");
scheduler.initialize();
return new DelegatingSecurityContextTaskScheduler(scheduler);
}
@Bean
public ExecutorService executorService() {
ExecutorService service = Executors.newFixedThreadPool(10);
return new DelegatingSecurityContextExecutorService(service);
}
}
// Usage
@Service
public class BackgroundService {
@Autowired
private TaskExecutor taskExecutor;
@Autowired
private TaskScheduler taskScheduler;
public void executeBackgroundTask() {
// Current security context will be propagated
taskExecutor.execute(() -> {
Authentication auth = SecurityContextHolder.getContext()
.getAuthentication();
// Process with authenticated context
});
}
public void scheduleTask() {
taskScheduler.scheduleAtFixedRate(() -> {
Authentication auth = SecurityContextHolder.getContext()
.getAuthentication();
// Scheduled task with security context
}, 1000);
}
}Use Delegating Executors: Always wrap executors with delegating variants when security context is needed in background threads.
Avoid InheritableThreadLocal with Executors: Executors reuse threads, so inheritable thread local can cause context leakage. Use delegating executors instead.
Clear Context After Use: Delegating executors automatically clear context after task execution, but be aware when manually managing context.
Context is Copied: Security context is copied (not shared) to child threads, so modifications in child threads don't affect parent.
// Good: Use delegating executor
ExecutorService service = Executors.newFixedThreadPool(10);
DelegatingSecurityContextExecutorService secureService =
new DelegatingSecurityContextExecutorService(service);
// Bad: Relying on inheritable thread local with executors
SecurityContextHolder.setStrategyName(
SecurityContextHolder.MODE_INHERITABLETHREADLOCAL
);
// Executor threads are reused, context can leak between tasksorg.springframework.security.concurrentorg.springframework.security.core.context