So this is all (unsurprisingly) by design. These spring security docs give a good explanation as to what's happening - quoting:
In an application which receives concurrent requests in a single session, the same SecurityContext
instance will be shared between threads. Even though a ThreadLocal
is being used, it is the same instance that is retrieved from the HttpSession
for each thread. This has implications if you wish to temporarily change the context under which a thread is running. If you just use SecurityContextHolder.getContext()
, and call setAuthentication(anAuthentication)
on the returned context object, then the Authentication
object will change in all concurrent threads which share the same SecurityContext
instance. You can customize the behaviour of SecurityContextPersistenceFilter
to create a completely new SecurityContext
for each request, preventing changes in one thread from affecting another. Alternatively you can create a new instance just at the point where you temporarily change the context. The method SecurityContextHolder.createEmptyContext()
always returns a new context instance.
A snippet of the above quote says:
... you can customise the behaviour of SpringContextPersistenceFilter
...
Unfortunately, the docs do not provide any information as to how to go about doing this, or how it would even be approached. This SO question asks the very thing (essentially, is a distilled version of this question), but it has not received much attention.
There's also this SO answer that provides a bit more depth into the inner workings of HttpSessionSecurityContextRepository
, which is likely to be the piece that would need to be re-written/updated in order to tackle this issue.
I'll update this answer if I come by a good way of addressing this (such as creating a new instance of the context) in implementation.
Update
The root of the problem I'm experiencing was related to reading a user id attribute out of the HttpSession
(after it had been cleared by a concurrent "logout" request). Instead of implementing my own SpringContextRepository
I decided to create a simple filter that saves the current Authentication
to the request, and then work from there.
Here's the basic filter:
public class SaveAuthToRequestFilter extends OncePerRequestFilter {
public static final String REQUEST_ATTR = SaveAuthToRequestFilter.class.getCanonicalName();
@Override
protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain)
throws ServletException, IOException {
final SecurityContext context = SecurityContextHolder.getContext();
if (context != null) {
request.setAttribute(REQUEST_ATTR, context.getAuthentication());
}
filterChain.doFilter(request, response);
}
}
Which has to be added after the SecurityContextPersistenceFilter
by adding the following to your WebSecurityConfigurerAdapter
's configure(final HttpSecurity http)
method.
http.addFilterAfter(new SaveAuthToRequestFilter(), SecurityContextPersistenceFilter.class)
After you have that in place, you can read the 'current' (per thread/request) Authentication
from the HttpServletRequest
(@Autowired
or injected into your controller methods) and work from there. I think this solution still leaves something to be desired, but it's the most lightweight option I could think of. Thanks to @chimmi and @sura2k for the inspiration.