Spring Security: Passing Security_Context Across Threads

Spring Security: Passing Security_Context Across Threads

In Spring Security, the SecurityContext is a central component for managing authentication and authorization. It stores security-related information, such as the authenticated user, roles, and authentication tokens. By default, it uses a ThreadLocal-based holding strategy, which ties the SecurityContext to the current thread. While this works well in single-threaded applications, challenges arise when you need to pass the SecurityContext across threads in multi-threaded or asynchronous scenarios.

I tried in this article explains how the holding strategy works, how to pass the SecurityContext to different threads, and the special utilities Spring Security provides to make this easier.


Article content

The security context of Spring Security is described by the SecurityContext

interface. The following listing defines this interface :

public interface SecurityContext extends Serializable {
Authentication getAuthentication();
void setAuthentication(Authentication authentication);
}        

As you can observe from the contract definition, the primary responsibility of the SecurityContext is to store the Authentication object. But how is the SecurityContext itself managed? Spring Security offers three strategies to manage the SecurityContext with an object in the role of a manager. It’s named the SecurityContextHolder :

  • MODE_THREADLOCAL—Allows each thread to store its own details in the security context. In a thread-per-request web application, this is a common approach as each request has an individual thread.
  • MODE_INHERITABLETHREADLOCAL—Similar to MODE_THREADLOCAL but also instructs Spring Security to copy the security context to the next thread in case of an asynchronous method. This way, we can say that the new thread running the @Async method inherits the security context. The @Async annotation is used with methods to instruct Spring to call the annotated method on a separate thread.
  • MODE_GLOBAL—Makes all the threads of the application see the same security context instance.

Default Holding Strategy for SecurityContext

The SecurityContextHolder is the main utility for storing and accessing the SecurityContext. By default, it uses a ThreadLocal to isolate the context for each thread, When a user logs in, Spring Security stores the SecurityContext in this ThreadLocal, making it available only to the current thread. At the end of the request, the SecurityContext is cleared to avoid memory leaks:

SecurityContextHolder.clearContext();        

Challenges in Multi-threaded Applications

In multi-threaded environments, the ThreadLocal strategy becomes problematic because:

  • Threads spawned by the parent thread (e.g., using @Async or thread pools) do not inherit the parent thread's SecurityContext.
  • In reactive programming (where threads switch frequently), the SecurityContext tied to a specific ThreadLocal becomes unreliable.
  • Parallel processing (e.g., Java Streams or thread pools) requires explicitly propagating the SecurityContext to worker threads.

Without proper propagation, the SecurityContext is unavailable in new threads, leading to NullPointerException or security violations.

Make sample to understand main problem :

@GetMapping("/hello")
public String hello() {
SecurityContext context = SecurityContextHolder.getContext();
Authentication a = context.getAuthentication();
return "Hello, " + a.getName() + "!";
}// get name in context properly 
//or do like this : 
@GetMapping("/hello")
public String hello(Authentication a) {
return "Hello, " + a.getName() + "!";
}

//curl -u user:99ff79e3-8ca0-401c-a396-0a8625ab3bad http://localhost:8080/hello
Hello, user!
        

But when we have method call in different Thread problem raised like this :

@GetMapping("/bye")
@Async
public void goodbye() {
SecurityContext context = SecurityContextHolder.getContext();
String username = context.getAuthentication().getName();
// now error 
}        

If you try the code as it is now, it throws a NullPointerException on the line that gets the name from the authentication, which is

String username = context.getAuthentication().getName()        

This is because the method executes now on another thread that does not inherit the security context. For this reason, the Authorization object is null and, in the context of the presented code, causes a NullPointerException.

Strategies to Pass SecurityContext Across Threads

To address this, Spring Security provides several strategies and utilities to propagate the SecurityContext across threads:

1. Use MODE_INHERITABLETHREADLOCAL

If you need to propagate the SecurityContext to child threads, you can switch to an inheritable thread-local strategy:

@Configuration
@EnableAsync
public class ProjectConfig {
@Bean
public InitializingBean initializingBean() {
return () -> SecurityContextHolder.setStrategyName(
SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
 }
}         

This ensures that child threads inherit the SecurityContext of the parent thread. For example, this is useful when a thread spawns another thread for background processing:

SecurityContext context = SecurityContextHolder.getContext();
Thread childThread = new Thread(() -> {
    System.out.println(SecurityContextHolder.getContext().getAuthentication().getName());
});
childThread.start();        

  • Use Case: Useful for forked child threads.
  • Limitation: Does not work when using pooled threads (e.g., thread pools).

2. MODE_GLOBAL

In rare cases, you may want a global, shared SecurityContext, where all threads share the same context:

SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_GLOBAL);        

This makes the SecurityContext available to all threads globally. However, it breaks thread isolation and is not recommended for concurrent applications.

  • Use Case: Suitable only for testing or non-concurrent environments.
  • Limitation: No isolation between threads, which can lead to security risks.

3. Custom Holding Strategies

For advanced use cases, you can implement a custom holding strategy by creating a class that implements the SecurityContextHolderStrategy interface. This allows you to define exactly how and where the SecurityContext is stored.

Example :

public class CustomSecurityContextHolderStrategy implements SecurityContextHolderStrategy {
    private static final Map<String, SecurityContext> contextMap = new ConcurrentHashMap<>();

    @Override
    public void clearContext() {
        contextMap.remove(Thread.currentThread().getName());
    }

    @Override
    public SecurityContext getContext() {
        return contextMap.getOrDefault(Thread.currentThread().getName(), SecurityContextHolder.createEmptyContext());
    }

    @Override
    public void setContext(SecurityContext context) {
        contextMap.put(Thread.currentThread().getName(), context);
    }

    @Override
    public SecurityContext createEmptyContext() {
        return new SecurityContextImpl();
    }
}        

You can configure Spring Security to use this strategy:

SecurityContextHolder.setStrategyName("fully.qualified.class.name.of.CustomSecurityContextHolderStrategy");        

  • Use Case: When you need to store the SecurityContext in a custom location, such as a distributed cache or database.
  • Limitation: Requires careful implementation to ensure thread safety and avoid memory leaks.

4. Manual Propagation of SecurityContext

If you cannot use Spring's utilities, you can manually capture and propagate the SecurityContext to other threads. This involves capturing the context in the parent thread and restoring it in the child thread.

Example:


SecurityContext context = SecurityContextHolder.getContext();

Runnable task = () -> {
    SecurityContextHolder.setContext(context); // Restore context
    try {
        // Perform secured operations
        System.out.println(SecurityContextHolder.getContext().getAuthentication().getName());
    } finally {
        SecurityContextHolder.clearContext(); // Clear context to avoid leaks
    }
};

// Execute in a new thread
new Thread(task).start();        

  • How it Works: The parent thread passes its SecurityContext to the child thread explicitly.
  • Use Case: Works in environments where Spring's utilities cannot be used.
  • Limitation: Error-prone and requires careful management of context cleanup.


5. Using DelegatingSecurityContextRunnable or DelegatingSecurityContextCallable

For lightweight use cases, Spring Security provides DelegatingSecurityContextRunnable and DelegatingSecurityContextCallable to wrap Runnable or Callable tasks with the SecurityContext.

Example:

import org.springframework.security.concurrent.DelegatingSecurityContextRunnable;

SecurityContext context = SecurityContextHolder.getContext();

Runnable task = () -> {
    // Task logic with SecurityContext
    System.out.println(SecurityContextHolder.getContext().getAuthentication().getName());
};

// Wrap the task
Runnable secureTask = new DelegatingSecurityContextRunnable(task, context);

// Run the task in a new thread
new Thread(secureTask).start();        

  • How it Works: These classes restore the captured SecurityContext in the thread where the task executes.
  • Use Case: Suitable for ad hoc thread creation or simple asynchronous tasks.


6. DelegatingSecurityContextExecutor/ExecutorService

Spring Security offers DelegatingSecurityContextExecutor and DelegatingSecurityContextExecutorService to automatically propagate the SecurityContext when submitting tasks to an executor.

Example:

import org.springframework.security.concurrent.DelegatingSecurityContextExecutorService;

ExecutorService originalExecutor = Executors.newFixedThreadPool(10);
ExecutorService delegatingExecutor = new DelegatingSecurityContextExecutorService(originalExecutor);

// Submit tasks with propagated SecurityContext
delegatingExecutor.submit(() -> {
    // SecurityContext is available in this thread
    System.out.println(SecurityContextHolder.getContext().getAuthentication().getName());
});        

  • How it Works: These classes capture the current SecurityContext when a task is submitted and restore it in the worker thread before execution.
  • Use Case: Ideal for thread pools and asynchronous task execution.


7. Reactive Applications with Context Propagation

In reactive programming (e.g., using Project Reactor), threads change frequently, making ThreadLocal storage unreliable. Spring Security integrates with Reactor to propagate the SecurityContext using a ReactorContext.

Example:

import reactor.core.publisher.Mono;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;

Mono.deferContextual(context -> {
    Authentication auth = context.get(Authentication.class); // Retrieve the SecurityContext
    return Mono.just(auth.getName());
})
.contextWrite(ReactiveSecurityContextHolder.withAuthentication(authentication)) // Set SecurityContext
.subscribe(System.out::println);        

  • How it Works: The SecurityContext is stored in the Reactor context and propagated across the reactive chain.
  • Use Case: Suitable for reactive applications using Project Reactor or WebFlux.


First solution used different Strategy :

@Configuration
@EnableAsync
public class ProjectConfig {
@Bean
public InitializingBean initializingBean() {
return () -> SecurityContextHolder.setStrategyName(
SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
 }
}         

Best Practices

  1. Use Spring's Utilities: Prefer DelegatingSecurityContextExecutor or DelegatingSecurityContextRunnable to handle SecurityContext propagation automatically.
  2. Avoid Manual Propagation: While manual propagation works, it is error-prone and requires careful cleanup to prevent memory leaks.
  3. Test Security Context Propagation: In multi-threaded and reactive applications, test thoroughly to ensure the SecurityContext is propagated correctly.
  4. Clear Context After Use: Always clear the SecurityContext in worker threads to avoid leakage into other tasks.

Conclusion

Passing the SecurityContext across threads is critical for maintaining authentication and authorization in multi-threaded and asynchronous applications. Spring Security offers several strategies and special utilities, such as DelegatingSecurityContextExecutor and DelegatingSecurityContextRunnable, to simplify context propagation. In reactive applications, Spring Security integrates seamlessly with Reactor to manage context propagation.

By choosing the right strategy and following best practices, you can ensure your application's security context is handled reliably, even in complex threading scenarios.

What strategies do you use to handle SecurityContext in your multi-threaded applications? Share your thoughts in the comments below!

This merged explanation covers the holding strategy and strategies for passing the SecurityContext across threads, offering a complete and professional overview. Let me know if you need further adjustments!

Reference : spring-security-in-action-second-edition-meap-v6


Very informative.

To view or add a comment, sign in

More articles by kiarash shamaei

Insights from the community

Others also viewed

Explore topics