SpringSecurity中,SecurityContextHolder工具类初步解析

发布时间 2023-10-20 16:39:16作者: rockdow

Spring Security 中最基本的组件应该是SecurityContextHolder了。这是一个工具类,只提供一些静态方法。这个工具类的目的是用来保存应用程序中当前使用人的安全上下文。

一个应用可能有多个用户,SecurityContextHolder中使用ThreadLocal机制保存每个使用者的安全上下文,具体可看源码:

 private static void initializeStrategy() {
        if ("MODE_PRE_INITIALIZED".equals(strategyName)) {
            Assert.state(strategy != null, "When using MODE_PRE_INITIALIZED, setContextHolderStrategy must be called with the fully constructed strategy");
        } else {
            if (!StringUtils.hasText(strategyName)) {
                strategyName = "MODE_THREADLOCAL";
            }

            if (strategyName.equals("MODE_THREADLOCAL")) {
                strategy = new ThreadLocalSecurityContextHolderStrategy();
            } else if (strategyName.equals("MODE_INHERITABLETHREADLOCAL")) {
                strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
            } else if (strategyName.equals("MODE_GLOBAL")) {
                strategy = new GlobalSecurityContextHolderStrategy();
            } else {
                try {
                    Class<?> clazz = Class.forName(strategyName);
                    Constructor<?> customStrategy = clazz.getConstructor();
                    strategy = (SecurityContextHolderStrategy)customStrategy.newInstance();
                } catch (Exception var2) {
                    ReflectionUtils.handleReflectionException(var2);
                }

            }
        }
    }

其中strategyNameSecurityContextHolder的私有成员变量,SecurityContextHolder有三种工作模式

  • MODE_THREADLOCAL(默认模式)
  • MODE_INHERITABLETHREADLOCAL
  • MODE_GLOBAL

以MODE_THREADLOCAL为例,介绍ThreadLocalSecurityContextHolderStrategy类,代码如下:

final class ThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {
    private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal();

    ThreadLocalSecurityContextHolderStrategy() {
    }

    public void clearContext() {
        contextHolder.remove();
    }

    public SecurityContext getContext() {
        SecurityContext ctx = (SecurityContext)contextHolder.get();
        if (ctx == null) {
            ctx = this.createEmptyContext();
            contextHolder.set(ctx);
        }

        return ctx;
    }

    public void setContext(SecurityContext context) {
        Assert.notNull(context, "Only non-null SecurityContext instances are permitted");
        contextHolder.set(context);
    }

    public SecurityContext createEmptyContext() {
        return new SecurityContextImpl();
    }
}

可以看到,初始化了ThreadLocal类型的私有成员变量contextHolder,在getContext()方法中,使用contextHolder.set(ctx),以contextHolder作为键,向不同主线程的ThreadLocalMap类型的成员变量中放置了不同的Context对象,用来存储不同的用户信息。
接下来介绍一下三种工作模式的特点:

MODE_THREADLOCAL(默认模式):将SecurityContext 放在当前线程的 ThreadLocal 变量中。这意味着在同一线程内,不同的方法和代码段都可以访问相同的 SecurityContext,并因此共享用户数据。但是,当开启子线程时,子线程无法获取到用户数据,因为 ThreadLocal 不会被自动传递给子线程。

MODE_INHERITABLETHREADLOCAL:与 MODE_THREADLOCAL 类似,SecurityContext 也存储在 ThreadLocal 中,但它允许子线程访问相同的 SecurityContext。当你在主线程中创建子线程时,SecurityContext 可以被子线程继承,这意味着子线程也能够访问用户数据。

MODE_GLOBAL:数据保存到一个静态变量中,而不是线程本地变量。这种模式在 web 开发中很少使用,因为它将共享相同的 SecurityContext 实例给所有线程,可能会导致并发问题。一般情况下,它不适合多线程环境,因此在 web 应用中并不常见。

再以一段代码阐述MODE_THREADLOCAL的含义:

public class MyTask implements Runnable {
    @Override
    public void run() {
        // 在子线程中,没有自动传递的 SecurityContext
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        // authentication 为 null
    }
}

public static void main(String[] args) {
    // 在主线程中设置 SecurityContext
    SecurityContextHolder.getContext().setAuthentication(/* ... */);
    
    // 启动子线程
    Thread thread = new Thread(new MyTask());
    thread.start();
}