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.

1
2
3
4
5
6
7
public class SecurityContextHolder {
    public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";
    public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";
    public static final String MODE_GLOBAL = "MODE_GLOBAL";
    ...
    ...
}

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@Test
void contextLoads() {
    ThreadLocal threadLocal = new ThreadLocal();
    threadLocal.set("javaboy");
    System.out.println("threadLocal.get() = " + threadLocal.get());
    new Thread(new Runnable() {
        @Override
        public void run() {
            String name = Thread.currentThread().getName();
            System.out.println("name+threadLocal.get() = " + name + ":" + threadLocal.get());
        }
    }).start();
}

The printout of this code is, I believe, clear to everyone.

1
2
threadLocal.get() = javaboy
name+threadLocal.get() = Thread-121:null

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@Test
void contextLoads() {
    ThreadLocal threadLocal = new InheritableThreadLocal();
    threadLocal.set("javaboy");
    System.out.println("threadLocal.get() = " + threadLocal.get());
    new Thread(new Runnable() {
        @Override
        public void run() {
            String name = Thread.currentThread().getName();
            System.out.println("name+threadLocal.get() = " + name + ":" + threadLocal.get());
        }
    }).start();
}

The results of the run at this point change, as follows.

1
2
threadLocal.get() = javaboy
name+threadLocal.get() = Thread-121:javaboy

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.

1
2
3
4
5
6
7
8
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

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.

1
2
3
4
5
6
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    protected T childValue(T parentValue) {
        return parentValue;
    }
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    ...
    ...
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    ...
    ...
}

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
@GetMapping("/user")
public void userInfo() {
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    String name = authentication.getName();
    Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
    System.out.println("name = " + name);
    System.out.println("authorities = " + authorities);
    new Thread(new Runnable() {
        @Override
        public void run() {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            String name = authentication.getName();
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + ":name = " + name);
            System.out.println(threadName + ":authorities = " + authorities);
        }
    }).start();
}

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.

1
-Dspring.security.strategy=MODE_INHERITABLETHREADLOCAL

SecurityContextHolder

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/