SecurityContextHolder实现原理
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在实际项目中的应用,希望对大家有用!

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





