SpringSecurity Filter WebAsyncManager IntegrationFilter

Original Address

Starting with ThreadLocal, ThreadLocal can be understood as a Map bound to the current thread, where key is the current thread and value is the object to store. When we are in the same thread, we can get the value through the get() method. If get () is called on the A thread set(object) and then on the B thread, the key is A because the thread has switched, so taking B naturally does not take out the value with the key being A.

For an SSO system, we often put information about the currently logged-in user into ThreadLocal, avoiding invoking RPC or DB interfaces to query every time we use them. Spring Security's SecurityContextHolder is implemented through ThreadLocal.

WebAsyncManager, Spring document has a sentence: The central class for management asynchronous request processing, mainly intended as an SPI and not typically used directly by application classes. It is mainly used for asynchronous requests, and the author finds that it is used a lot in the distribution processing of spring mvc requests with limited time and no in-depth understanding. Our main focus is WebAsyncManagerIntegrationFilter.

public final class WebAsyncManagerIntegrationFilter extends OncePerRequestFilter {

	private static final Object CALLABLE_INTERCEPTOR_KEY = new Object();

	@Override
	protected void doFilterInternal(HttpServletRequest request,
			HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
			
		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
		
		SecurityContextCallableProcessingInterceptor securityProcessingInterceptor = (SecurityContextCallableProcessingInterceptor) asyncManager.getCallableInterceptor(CALLABLE_INTERCEPTOR_KEY);
		
		if (securityProcessingInterceptor == null) {
			asyncManager.registerCallableInterceptor(CALLABLE_INTERCEPTOR_KEY,new SecurityContextCallableProcessingInterceptor());
		}

		filterChain.doFilter(request, response);
	}
}

SecurityContextCallable Processing Interceptor, the key role of this interceptor, is to store and empty the SecurityContext before and after execution in order to obtain the SecurityContext in the WebAsyncTask asynchronous call with the following core code:

public final class SecurityContextCallableProcessingInterceptor extends
		CallableProcessingInterceptorAdapter {
	
	private volatile SecurityContext securityContext;


	public SecurityContextCallableProcessingInterceptor() {
	}

	public SecurityContextCallableProcessingInterceptor(SecurityContext securityContext) {
		Assert.notNull(securityContext, "securityContext cannot be null");
		setSecurityContext(securityContext);
	}

	@Override
	public <T> void beforeConcurrentHandling(NativeWebRequest request, Callable<T> task) {
		if (securityContext == null) {
			setSecurityContext(SecurityContextHolder.getContext());
		}
	}

	@Override
	public <T> void preProcess(NativeWebRequest request, Callable<T> task) {
		SecurityContextHolder.setContext(securityContext);
	}

	@Override
	public <T> void postProcess(NativeWebRequest request, Callable<T> task,
			Object concurrentResult) {
		SecurityContextHolder.clearContext();
	}

	private void setSecurityContext(SecurityContext securityContext) {
		this.securityContext = securityContext;
	}
}

The author has made a practice of trying to get the current login user from the SecurityContextHolder in a Runnable and WebAsyncTask respectively. AuthHolder just encapsulates the SecurityContextHolder and essentially calls the SecurityContextHolder.get() Gets thread context information in the following code:

@GetMapping("/run_async_task")
    public Map<String,String> getContextByRunnable() throws Exception {
        System.out.println("main thread[{"+Thread.currentThread().getName()+"}]start");
        Map<String,String> map = new HashMap<>();
        CountDownLatch latch = new CountDownLatch(1);
        Thread async = new Thread(() -> {
            try {
                System.out.println("Runnable Asynchronous Threads[{"+Thread.currentThread().getName()+"}], Start executing asynchronous tasks");
                Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
                if(null==authentication){
                    System.out.println("Failed to get authentication information");
                }else {
                    String name = authentication.getName();
                    map.put("name",name);
                }
                
                System.out.println("Runnable Asynchronous Threads[{"+Thread.currentThread().getName()+"}], Task Execution Complete,The current user is: "+map);
            } finally {
                latch.countDown();
            }
        });
        async.start();
        latch.await();
        System.out.println("main thread[{"+Thread.currentThread().getName()+"}]End");
        return map;
    }
}

main thread [{http-nio-8081-exec-4}] starts
Runnable asynchronous thread [{Thread-11}], start executing asynchronous task
Failed to get authentication information
Runnable asynchronous thread [{Thread-11}], task execution complete, current user is: {}
main thread [{http-nio-8081-exec-4}] ends

@GetMapping("/web_async_task")
public WebAsyncTask getContextByWebAsyncTask() {
       System.out.println("main thread[{"+Thread.currentThread().getName()+"}]start");
       WebAsyncTask<String> task = new WebAsyncTask<>(300L, () -> {
           System.out.println("WebAsyncTask Asynchronous Threads[{"+Thread.currentThread().getName()+"}], Start executing asynchronous tasks");
           String name = SecurityContextHolder.getContext().getAuthentication().getName();

           System.out.println("WebAsyncTask Asynchronous Threads[{"+Thread.currentThread().getName()+"}], Task Execution Complete,The current user is: "+name);
           return name;
       });
       System.out.println("main thread[{"+Thread.currentThread().getName()+"}]End");
       return task;
}

main thread [{http-nio-8081-exec-6}] starts
main thread [{http-nio-8081-exec-6}] ends
WebAsyncTask asynchronous thread [{task-1}], start executing asynchronous task
WebAsyncTask asynchronous thread [{task-1}], task execution complete, current user is:user

As you can see, WebAsyncTask gets authentication information from the SecurityContextHolder, but Runnable does not. The ability of WebAsyncTask to obtain context information from the SecurityContextHolder in an asynchronous thread is closely related to the role of the WebAsyncManager IntegrationFilter.

Keywords: Java Spring Microservices Spring Security

Added by liza on Wed, 05 Jan 2022 20:05:52 +0200