Reputation: 37034
I use spring security in my project.
I have feature to change login. To achieve this aim I use following code
Authentication authentication = ...
SecurityContextHolder.getContext().setAuthentication(authentication);
But now I am resesarching this code in details and see that authentication field is not volatile
thus visibility is not guaranteed:
public class SecurityContextImpl implements SecurityContext {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
// ~ Instance fields
// ================================================================================================
private Authentication authentication;
Should I wrap my code with my own synchronization to achieve visibility?
I have read https://stackoverflow.com/a/30781541/2674303
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.
but I don't understand how spring does guaranties visibility. There are just written that each thread within session will see changes. but there is no answer how fast? and more important - visibility mechanism is not explained
Upvotes: 23
Views: 1383
Reputation: 4657
Java has a set of strict and well-defined rules that dictate memory consistency. In short, some things are guaranteed to happen-before other things. You can read about it here. One way of achieving happens-before is through keyword volatile
, and another is through synchronized
.
As with any documentation, if Spring is not explicitly clear on what it does guarantee for you, then you should not assume that it guarantees what you want.
In short, you should wrap your code that changes or uses variable in synchronized
block. This will guarantee that value of a variable does not change until you are finished using it. Spring might update variables for you, but it won't guarantee neither cross-thread visibility nor synchronization because only your code knows when and how you use that variable.
Note you should use synchronized (x)
where x is something global, constant, fixed and shared between all threads (i.e. MyClass.class) - NOT the variable itself.
Upvotes: 0
Reputation: 9848
If you are not creating new threads or using @Async
, then your approach of setting your new authentication on the SecurityContext is correct. If you do create new threads, then it falls into two use cases: new threads are created before or after you switch authentication. If your threads are created after the new authentication object is set, then you'll have to create a thread factory that is authentication aware and will copy the Authentication object to whatever threads it creates. If your threads are created before, then take a look at AuthenticationSuccessEvent
and AuthenticationEventPublisher
, they are Spring's official way of signaling auth events, there might be some useful mechanism for propagating authentication changes across all threads currently used that user.
In one of my previous applications I had to implement functionality where admin users could impersonate regular users in order to help them debug issues with the app, SecurityContextHolder.getContext().setAuthentication(authentication);
you describe was used and we never ran into any issues with Spring confusing which user it should perform actions as. The app ran on a multi node cluster with a common session cache shared by all the nodes.
Upvotes: 0
Reputation: 2686
Your doubts are justified, visibility is not guaranteed. ThreadLocal isn't thread-safe, when all ThreadLocalMap's entries store the same object.
Referenced documentation part, Storing the SecurityContext between requests, warns you about that fact and proposes possible solutions to change the context in a way, that prevents affect on other threads.
An example of such solution is the RunAs mechanism, which changes the context during the secure object callback phase.
But, as I understand your question, you need to change the user's login (i.e. username) "on the fly". If I'm right, then the problem is, when you'd set a modified Authentication
- another thread could read the old value. To avoid this race condition you need to make a login write happens-before every sequential login read.
Authentication
interface has the getPrincipal()
method, which returns an Object
, which is an UserDetails
instance (in most cases). This object is usually used to obtain a username for the current (authenticated) user.
So, if you want to change the login for authenticated user "on the fly", you can modify the username
property in this UserDetails
object.
Possible way to make it in a thread-safe manner is a custom UserDetails
implementation with the volatile String username
property (default User
implementation has an immutable username).
You should also create and wire into your configuration an UserDetailsService
implementation, which will use your custom UserDetails
.
Upvotes: 9