We are using Spring Security in our web application. Most of the pages are secured, i.e. a user must be logged in to access these pages. It works fine usually. However, we encounter an unwanted behavior during logout.
Suppose that a user is logged in and sends a request to the sever to load some (secured) page. Before this request is completed, the same user sends a logout request (i.e. request with servlet_path "/j_spring_security_logout"). The logout request is usually very fast and it can be completed earlier than the former request. Of course, the logout request clears the security context. Hence, the former request loses the security context in the middle of its life and this usually cause an exception.
In fact, the user need not start the first request "manually". This scenario can happen on a page with automatic refresh, i.e. the user presses the logout link just a fraction of second after a refreshing has been sent automatically.
From one point of view, this can be considered to be a meaningful behavior. On the other hand, I would prefer to prevent such loss of security context in the middle of the life of a request.
Is there a way to configure Spring Security to avoid this? (something like "postpone clearing of security context when there are other concurrent requests from the same session" or "read the security context just once during a single request and cache it for further use")
Thanks.
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 aThreadLocal
is being used, it is the same instance that is retrieved from theHttpSession
for each thread. This has implications if you wish to temporarily change the context under which a thread is running. If you just useSecurityContextHolder.getContext()
, and callsetAuthentication(anAuthentication)
on the returned context object, then theAuthentication
object will change in all concurrent threads which share the sameSecurityContext
instance. You can customize the behaviour ofSecurityContextPersistenceFilter
to create a completely newSecurityContext
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 methodSecurityContextHolder.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.
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.
Your requirement was reported as a bug (not a feature request) in JIRA SEC-2025. And They have fixed it in Spring 3.2, so which you expect here to happen/solve implicitly prevents by its design.
Default behavior is to save the SecurityContext
in the HttpSession
and that is the only implementation afaik spring security provides at the moment.
Even the SecurityContext
is ThreadLocal
it shares the same which is in the HttpSession
. So when SecurityContext
gets cleaned, it will remove from the HttpSession
, hence will be unavailable from the entire user session.
What you need to do is, store the SecurityContext
additionally in HttpServletRequest
(or something bound to the HTTP request) other than the HttpSession
and read it back from HttpSession
and if not found, read it from HttpServletRequest
. Make sure to save a deep-copy of SecurityContext
in the HttpServletRequest
. When you logs out, clean the SecurityContext
only from HttpSession which is currently happening. In this case whatever the running threads (bound to HTTP requests) will have access to the SecurityContext
via HttpServletRequest
(if it not found in HttpSession
- which is happening to you right now) even if the user has logged out. Next new HTTP requests will need an authentication because new requests has no SecurityContext
in the HttpSession
( or HttpServletRequest
).
Keep a new copy of SecurityContext
in each HttpServletRequest
may be an overhead only to address a corner case.
To do that you need to read and understand following spring implementations.
HttpSessionSecurityContextRepository class
SecurityContextPersistenceFilter
uses SecurityContextRepository
to load and save SecurityContext
. HttpSessionSecurityContextRepository
is an implemntation of SecurityContextRepository
.
SaveToSessionResponseWrapper class
And you might need to replace above 2 classes by providing your own implementations or overriding necessary implementations. (And there may some others as well)
Refer:
method implementations.
I think Spring Security docs clearly mentions not to deal with HttpSession
. That is why there is a SecurityContext
.
IMO just stick to the Spring Security implementations when there are no recommendations from Spring Security engineers to do something on your own. What I'm sugessting here may not correct, and make sure there will no security holes and it should not break the other use cases when you do some non-recommended changes only to cover a corner case. I will never do this if this happened to me, because it is the design that Spring Security experts have decided to go with by considering many many many security facts which I have no idea at all.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With