Security contextmedium3-5 years

A service method reads the current user with `SecurityContextHolder.getContext().getAuthentication()` and works in the request. The same method, called from an `@Async` method or inside `CompletableFuture.supplyAsync(...)`, sees no user. A teammate proposes `MODE_INHERITABLETHREADLOCAL`. Explain the empty context, and why that proposal can make things worse.

SecurityContextHolder is a ThreadLocal by default: each thread has its own value, set near the top of the filter chain on the request thread. Work handed to an executor runs on a different thread, and nothing copied the context there, so that thread sees an empty one. Inheritable mode copies the context only when a thread is created from the request thread. Pool threads were created long before this request and are reused, so they never inherit this request's context; worse, a pool thread created during some earlier request could carry that request's user into unrelated work. The fixes are to pass what you need (the user id) as an argument, or to wrap the executor so each task carries the submitting thread's context and clears it afterwards: DelegatingSecurityContextExecutor and its relatives.

The lesson behind it →