SecurityContextHolder实现原理(策略模式和ThreadLocal)

本文深入剖析了Spring Security中的SecurityContextHolder组件,它是保存认证信息的核心,利用策略模式和ThreadLocal实现线程安全的认证信息存储。讲解了SecurityContextHolder、SecurityContextHolderStrategy接口,特别是ThreadLocalSecurityContextHolderStrategy,以及ThreadLocal的工作原理,包括如何保存和获取线程局部变量。文章通过源码分析,帮助理解这两个技术在实际项目中的应用。

SecurityContextHolder是SpringSecurity中保存认证信息的核心组件,重点是将给定的认证信息(SecurityContext)与当前执行线程关联。也就是说在同一个线程中可以通过该组件随时方便的获得认证信息,基本操作

//保存认证信息
SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
//获得认证信息
SecurityContextHolder.getContext().getAuthentication()

一、核心组件

1.1 SecurityContextHolder

SecurityContextHolder源码如下,为方便阅读,增加中文注释:


package org.springframework.security.core.context;

import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Constructor;

/**
 * 将给定的SecurityContext与当前执行线程关联。该类的提供了一系列的静态方法,真正的实现者,均交由org.springframework.security.core.context.SecurityContextHolderStrategy的实现类来完成
 */
public class SecurityContextHolder {
	// ~ Static fields/initializers
	// =====================================================================================

	public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";
	public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";
	public static final String MODE_GLOBAL = "MODE_GLOBAL";
	public static final String SYSTEM_PROPERTY = "spring.security.strategy";
	private static String strategyName = System.getProperty(SYSTEM_PROPERTY);
	//被委托者,采用策略模式
	private static SecurityContextHolderStrategy strategy;
	private static int initializeCount = 0;

//当类进行加载的时候,需要进行初始化操作
	static {
		initialize();
	}

	// ~ Methods
	// ========================================================================================================

	/**
	 * 从当前线程显式清除上下文值。
	 */
	public static void clearContext() {
		strategy.clearContext();
	}

	/**
	 * 从当前线程获得上下文值。值非空
	 */
	public static SecurityContext getContext() {
		return strategy.getContext();
	}

	/**
	 * 主要用于故障排除目的,此方法显示的次数已重新初始化SecurityContextHolderStrategy的次数
	 * 默认返回1
	 */
	public static int getInitializeCount() {
		return initializeCount;
	}

/**
该初始化方法就是实例化策略对象SecurityContextHolderStrategy
security内置三种策略来操作认证信息(SecurityContext),当然也可以通过设置策略名,来指定自定义的策略
 */
	private static void initialize() {
		if ((strategyName == null) || "".equals(strategyName)) {
			// 默认采用ThreadLocal实现方式
			strategyName = MODE_THREADLOCAL;
		}

		if (strategyName.equals(MODE_THREADLOCAL)) {
		// 默认采用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 ex) {
				ReflectionUtils.handleReflectionException(ex);
			}
		}
//记录策略初始化的次数信息
		initializeCount++;
	}

	/**
	 * 在当前线程中绑定一个认证信息
	 */
	public static void setContext(SecurityContext context) {
		strategy.setContext(context);
	}

	/**
	 * 设置自定义操作SecurityContext的策略类
	 */
	public static void setStrategyName(String strategyName) {
		SecurityContextHolder.strategyName = strategyName;
		initialize();
	}

	/**
	 * 获得操作SecurityContext的策略类
	 */
	public static SecurityContextHolderStrategy getContextHolderStrategy() {
		return strategy;
	}

	/**
	 * 使用指定策略生成 新的空上下文的
	 */
	public static SecurityContext createEmptyContext() {
		return strategy.createEmptyContext();
	}
	public String toString() {
		return "SecurityContextHolder[strategy='" + strategyName + "'; initializeCount="
				+ initializeCount + "]";
	}
}

1.2 SecurityContextHolderStrategy

策略模式参考:一个男人的设计模式:策略模式
该策略接口源码

package org.springframework.security.core.context;

/**
 * 保存认证信息的策略接口
 * 该接口是SecurityContextHolder中委托实现的实际对象
 * 其框架实现有三个
 * 1.ThreadLocalSecurityContextHolderStrategy
 * 2.GlobalSecurityContextHolderStrategy
 * 3.InheritableThreadLocalSecurityContextHolderStrategy
 */
public interface SecurityContextHolderStrategy {
		/**
	 * Clears the current context.
	 */
	void clearContext();

	/**
	 * Obtains the current context.
	 *
	 * @return a context (never <code>null</code> - create a default implementation if
	 * necessary)
	 */
	SecurityContext getContext();

	/**
	 * Sets the current context.
	 *
	 * @param context to the new argument (should never be <code>null</code>, although
	 * implementations must check if <code>null</code> has been passed and throw an
	 * <code>IllegalArgumentException</code> in such cases)
	 */
	void setContext(SecurityContext context);

	/**
	 * Creates a new, empty context implementation, for use by
	 * <tt>SecurityContextRepository</tt> implementations, when creating a new context for
	 * the first time.
	 *
	 * @return the empty context.
	 */
	SecurityContext createEmptyContext();
}

方法很少,接口每个含义应该都是见名知意,不做过多的解释

1.3ThreadLocalSecurityContextHolderStrategy

核心的原理就是通过ThreadLocal来保存认证信息,具体的ThreadLocal实现原理,下一小节详细分析;

该接口主要关联的两个核心技术 ThreadLocal 和SecurityContextPersistenceFilter
ThreadLocal :线程局部变量
SecurityContextPersistenceFilter:该过滤器属于Security的核心过滤器之一,其作用是在进行后续认证操作之前,将SecurityContext认证信息保存到SecurityContextHolder中,以便在当前线程其它处理类方便的处理认证信息,当Security其它核心过滤器走完之后,SecurityContextPersistenceFilter会把认证过后的SecurityContext持久化到指定的介质中(redis,mysql等等),同时删除SecurityContextHolder保存的认证信息.


package org.springframework.security.core.context;
import org.springframework.util.Assert;

/**
基于线程局部变量的SecurityContextHolderStrategy
 * @see java.lang.ThreadLocal
 * @see org.springframework.security.core.context.web.SecurityContextPersistenceFilter
 */
final class ThreadLocalSecurityContextHolderStrategy implements
		SecurityContextHolderStrategy {
		//核心的原理就是通过ThreadLocal来保存认证信息,具体的ThreadLocal实现原理,下一小节详细分析
		private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<SecurityContext>();

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

	public SecurityContext getContext() {
		SecurityContext ctx = contextHolder.get();

		if (ctx == null) {
			ctx = 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源码分析

ThreadLocal完成的功能是每个线程通过同样的操作步骤,在不同的线程中可以得到自己线程特有的数据,即线程局部变量(保存在各线程中的变量)

1.1实现该功能的核心技术

保存数据到ThreadLocal中源码:

    public void set(T value) {
    //获得当前线程
        Thread t = Thread.currentThread();
         //获得当前线程中的ThreadLocalMap信息
        ThreadLocalMap map = getMap(t);
        //在当前线程中保存ThreadLocal到value的映射关系
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

从ThreadLocal获取数据源码

    public T get() {
    //获得当前线程
        Thread t = Thread.currentThread();
        //获得当前线程中的ThreadLocalMap信息
        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();
    }

从获取或设置数据的源码中,均通过Thread t = Thread.currentThread();获得了当前线程对象,然后执行了getMap方法
getMap源码如下,核心获得Thread中的保存特有数据的map:

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

其中threadLocals是Thread中的字段

    /* 该字段由ThreadLocal类来维护,在ThreadLocal的get或set信息时本质上是修改了该map中保存的信息 */
    ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocalMap是ThreadLocal中的一个static类
ThreadLocalMap保存数据

        private void set(ThreadLocal<?> key, Object value) {

            //保存局部变量信息的容器
            Entry[] tab = table;
            int len = tab.length;
            //通过位运算获得数组角标
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
			//组成Entry保存到数组中
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

ThreadLocalMap获取数据Entry

private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

1.2ThreadLocal总结

ThreadLocal通过操作Thread中的ThreadLocal.ThreadLocalMap threadLocals = null;来保存或获取线程对象的信息

本质上Thread中保存了自己的特有数据,而对自己特有的数据,是通过ThreadLocal这个帮助类来进行的,而不是程序员直接操作Thread中的ThreadLocal.ThreadLocalMap threadLocals 字段来完成的

总结

以上通过对SecurityContextHolder的源码剖析,可以学到策略模式和ThreadLocal在实际项目中的应用,希望对大家有用!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值