×
☰ See All Chapters

Delegating SecurityContext to self-managed threads in Spring Security

In coding, sometimes we take charge and start threads without the framework's knowledge, known as "self-managed threads." But what about the security context in these cases?

Spring Security provides handy tools to seamlessly transfer the security context to these new threads. When the usual methods fall short, DelegatingSecurityContextRunnable and DelegatingSecurityContextCallable step in. These extend Runnable and Callable, respectively, making sure the security context travels safely to new threads.

Spring Security also offers specialized executors like DelegatingSecurityContextExecutorService, ensuring security context transfer in thread pools. Need to schedule tasks? DelegatingSecurityContextScheduledExecutorService has your back.

In a nutshell, these tools ensure that your self-managed threads navigate securely through the security context, maintaining integrity along the way.

Here's a glance at Spring Security's stalwart defenders in the realm of thread safety:

  • DelegatingSecurityContextExecutor: Enhances the Executor interface, ensuring security context propagation within thread pools. 

  • DelegatingSecurityContextExecutorService: Elevates the ExecutorService interface, shielding thread pools with secure context propagation. 

  • DelegatingSecurityContextScheduledExecutorService: Bolsters the ScheduledExecutorService interface, enabling scheduled task execution with fortified security context propagation. 

  • DelegatingSecurityContextRunnable: A sentinel of Runnable tasks, ensuring security context propagation without return values. 

  • DelegatingSecurityContextCallable: The guardian of Callable tasks, guaranteeing security context propagation with eventual return values. 

package com.java4coding;

import lombok.extern.slf4j.Slf4j;
import org.springframework.security.concurrent.DelegatingSecurityContextCallable;
import org.springframework.security.concurrent.DelegatingSecurityContextExecutorService;
import org.springframework.security.concurrent.DelegatingSecurityContextRunnable;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@RestController
@Slf4j
public class DemoController {

   
/*
   You can use DelegatingSecurityContextRunnable following the execution
   of the task when there is no value expected.
    */
   
@GetMapping(value = "/hello")
   
public String sayHello() throws ExecutionException, InterruptedException {
       
Runnable task = () -> {
           SecurityContext context = SecurityContextHolder.getContext();
           
log.info(context.getAuthentication().getName());
       };
       
ExecutorService executorService = Executors.newCachedThreadPool();
       
try {
           
var contextTask = new DelegatingSecurityContextRunnable(task);
           
executorService.submit(contextTask);
           
return "Hi, Task submitted to run asynchronously -  no return value ";
       }
finally {
           
executorService.shutdown();
       }
   }

   
/*
   You can use DelegatingSecurityContextCallable following the execution of
   the task when return value expected.
    */
   
@GetMapping(value = "/hi")
   
public String sayHi() throws ExecutionException, InterruptedException {
       
Callable<String> task = () -> {
           
SecurityContext context = SecurityContextHolder.getContext();
           
return context.getAuthentication().getName();
       };
       
ExecutorService executorService = Executors.newCachedThreadPool();
       
try {
           
var contextTask = new DelegatingSecurityContextCallable<>(task);
           
return "Hi, " + executorService.submit(contextTask).get() + "!";
       }
finally {
           
executorService.shutdown();
       }
   }

   
/*
   DelegatingSecurityContextExecutorService decorates an ExecutorService and propagates
   the security context details to the next thread before submitting the task.
    */
   
@GetMapping(value = "/hurray")
   
public String sayHurray() throws ExecutionException, InterruptedException {
       
Callable<String> task = () -> {
           
SecurityContext context = SecurityContextHolder.getContext();
           
return context.getAuthentication().getName();
       };
       
ExecutorService executorService = Executors.newCachedThreadPool();
       
executorService = new DelegatingSecurityContextExecutorService(executorService);
       
try {
           
return "Hurray, " + executorService.submit(task).get() + "!";
       }
finally {
           
executorService.shutdown();
       }
   }
}

 

 


All Chapters
Author