If you want to get the login user information in Spring Security, you can’t get it in a child thread, only in the current thread. One important reason for this is that the SecurityContextHolder
stores user information in ThreadLocal by default.
However, the SecurityContextHolder
actually defines three storage policies.
The second storage strategy, MODE_INHERITABLETHREADLOCAL
, supports getting information about the currently logged-in user in a sub-thread, while MODE_INHERITABLETHREADLOCAL
uses InheritableThreadLocal
as its underlying layer.
So what is the difference between InheritableThreadLocal
and ThreadLocal
? Why does it support fetching data from child threads? This article will talk to you about this topic. If we understand this problem, we can understand why in Spring Security, we can get the current logged-in user information in a sub-thread with a little configuration.
1. A small demo
Let’s start with an example that you may have seen before.
|
|
The printout of this code is, I believe, clear to everyone.
The data will be read from the thread in which it is stored, and will not be read by the child threads. If we change the ThreadLocal
in the above case to InheritableThreadLocal
, as follows.
|
|
The results of the run at this point change, as follows.
As you can see, if you use InheritableThreadLocal
, you can get the data in the parent ThreadLocal even in the child thread.
How does this work? Let’s analyze it together.
2. ThreadLocal
Let’s analyze ThreadLocal first.
Without looking at the source code and analyzing ThreadLocal only from the point of view of usage, you will find that a ThreadLocal can only store one object, if you need to store multiple objects, you need multiple ThreadLocal.
Let’s look at the ThreadLocal source code and analyze it.
When we want to call the set method to store an object, it is as follows.
As you can see, the storage will first get a ThreadLocalMap object, and when you get it, you need to pass in the current thread, see here you may guess a few points, the data is stored in a ThreadLocalMap similar to the Map, ThreadLocalMap and thread association, no wonder each thread can only No wonder each thread can only get its own data. Next, let’s verify it and continue to look at the getMap method.
The getMap method returns a threadLocals variable, which means that data exists in threadLocals. threadLocals is a ThreadLocalMap
. data is actually stored in an Entry array in ThreadLocalMap
. In the same thread, a ThreadLocal can only save one object, if you need to save more than one object, you need more than one ThreadLocal, and the variables saved by more than one ThreadLocal in the same thread are actually in the same ThreadLocalMap
, that is, in the same Entry array. The variables saved by ThreadLocal in different threads are in different Entry arrays, the key in the Entry array is actually the ThreadLocal object, and the value is the data set in.
Let’s look at the data reading.
First get the corresponding ThreadLocalMap
according to the current thread, then pass in the current object to get the Entry, and then return the value in the Entry object. Some people may ask, isn’t Entry an array? Why don’t we pass in an array subscript to get Entry, instead of passing in the current ThreadLocal
object to get Entry? In fact, in the getEntry method, the subscript of the array is calculated based on the current object, and then the Entry is returned.
3. InheritableThreadLocal
InheritableThreadLocal
is actually a subclass of ThreadLocal
, so let’s take a look at the definition of InheritableThreadLocal.
|
|
As you can see, the main thing is to rewrite three methods.
The return value of the getMap method is now inheritableThreadLocals, and the inheritableThreadLocals constructed in the createMap method are still ThreadLocalMap
objects. Compared to ThreadLocal, the main thing is that the object that holds the data has changed from threadLocals to inheritableThreadLocals.
This change does not affect the get/set in ThreadLocal that we described earlier, i.e. the properties of ThreadLocal remain the same.
The change happens in the thread initialization method, let’s look at the Thread#init
method.
|
|
As you can see, when creating a child thread, if the parent thread has an inheritableThreadLocals variable and is not empty, the ThreadLocal.createInheritedMap
method is called to assign a value to the inheritableThreadLocals variable of the child thread. What the ThreadLocal.createInheritedMap
method does is actually assign the value of the parent thread’s inheritableThreadLocals variable to the child thread’s inheritableThreadLocals variable. As a result, the data in the parent thread’s ThreadLocal is accessible in the child thread.
It is important to note that this replication is not a real-time synchronization. There is a point in time where the value of the parent inheritableThreadLocals variable is assigned to the child thread at the moment it is created. Once the child thread is created successfully, if the user goes back and modifies the value of the parent inheritableThreadLocals variable (i.e. modifies the data in the parent ThreadLocal in the parent thread). The child thread will not be aware of this change.
After the above introduction, I believe you will be able to figure out the difference between ThreadLocal and InheritableThreadLocal.
4. SpringSecurity
Let’s start with a piece of code.
|
|
By default, the logged-in user information is not available to the methods in the child thread. This is because the data in the SecurityContextHolder is stored in ThreadLocal.
In SecurityContextHolder, the default data storage policy is obtained through System.getProperty, so we can modify the default data storage policy of SecurityContextHolder by modifying the system variables at project startup.
|
|
After the modification is completed, the project is started again and the logged-in user data is also available in the sub-thread.
Reference http://www.enmalvi.com/2021/06/24/springsecurity-inheritablethreadlocal/