SpringSecurity之SecurityContextHolder使用解读
作者:chihaihai
之前在使用SpringSecurity的过程中并没有把很多东西理解透彻,找了很多学习资料也都只是是很浅显了告诉你怎么使用这个东西。现在只能自己回过头来研究研究。。。。终究是一个人扛下了所有/(ㄒoㄒ)/~~。
GO,现在越看spring源码越觉得里面注释是真的详细,虽然英语很菜耐不住有翻译哈哈哈。
介绍
看了几篇大佬的技术博客里面都介绍到,SecurityContextHolder是保存全上下文对象(SecurityContext)的地方。对于这种说法我感觉是完全不正确的,如果后面有新的认知我会回来进行修改。
我认为SecurityContextHolder只是为SecurityContext提供一种存储策略,只是主导了他的存储方式及地址。
使用中我们表面上看起来SecurityContext的存储都是通过SecurityContextHolder在控制人们就习以为常的说成了Security Context存储在了SecurityContextHolder中,至于真相我们看源码慢慢分析。
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;
首先从源码可以看到SecurityContextHolder提供了一个SecurityContextHolderStrategy存储策略进行上下文的存储,进入到Security ContextHolderStrategy接口,由下图我们可以清晰的看到其总共有三个实现类。
分别对应三种存储策略,这里我不知道为什么99的文章都只说了threadlocal和global两种。
要不是看了下实现类我都信了。
就跟字面意思一样三种策略分别对应threadlocal,global,InheritableThreadLocal三种方式。
继续回到源码我们详细看这三种方式。
private static void initialize() { if (!StringUtils.hasText(strategyName)) { // 如果没有设置自定义的策略,就采用MODE_THREADLOCAL模式 strategyName = MODE_THREADLOCAL; } // ThreadLocal策略 if (strategyName.equals(MODE_THREADLOCAL)) { strategy = new ThreadLocalSecurityContextHolderStrategy(); } // 采用InheritableThreadLocal,它是ThreadLocal的一个子类 else if (strategyName.equals(MODE_INHERITABLETHREADLOCAL)) { strategy = new InheritableThreadLocalSecurityContextHolderStrategy(); } // 全局策略,实现方式就是static SecurityContext contextHolder else if (strategyName.equals(MODE_GLOBAL)) { strategy = new GlobalSecurityContextHolderStrategy(); } else { // Try to load a custom strategy 自定义的策略,通过返回创建出 try { Class<?> clazz = Class.forName(strategyName); Constructor<?> customStrategy = clazz.getConstructor(); strategy = (SecurityContextHolderStrategy) customStrategy.newInstance(); } catch (Exception ex) { ReflectionUtils.handleReflectionException(ex); } } initializeCount++; }
由源码我们可以得出SecurityContextHolder 默认使用的是THREADLOCAL模式,光从名字看感觉就是存储在threadlocal中的策略到底是不是我们去源码中分别一探究竟:
ThreadLocalSecurityContextHolderStrategy
final class ThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy { // ~ Static fields/initializers // ===================================================================================== private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>(); 。。。。。。 }
InheritableThreadLocalSecurityContextHolderStrategy
final class InheritableThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy { // ~ Static fields/initializers // ===================================================================================== private static final ThreadLocal<SecurityContext> contextHolder = new InheritableThreadLocal<>(); 。。。。。。 }
GlobalSecurityContextHolderStrategy
final class GlobalSecurityContextHolderStrategy implements SecurityContextHolderStrategy { // ~ Static fields/initializers // ===================================================================================== private static SecurityContext contextHolder; 。。。。。。 }
到这里那些说SecurityContext存储在SecurityContextHolder的大佬们我认为应该是不严谨的。
默认是将SecurityContext存储在threadlocal中,可能是spring考虑到目前大多数为BS应用,一个应用同时可能有多个使用者,每个使用者又对应不同的安全上下,Security Context Holder为了保存这些安全上下文。
缺省情况下,使用了ThreadLocal机制来保存每个使用者的安全上下文。
因为缺省情况下根据Servlet规范,一个Servlet request的处理不管经历了多少个Filter,自始至终都由同一个线程来完成。这样就很好的保证了其安全性。
但是当我们开发的是一个CS本地应用的时候,这种模式就不太适用了。
spring早早的就考虑到了这种情况,这个时候我们就可以设置为Global模式仅使用一个变量来存储SecurityContext。比如还有其他的一些应用会有自己的线程创建,并且希望这些新建线程也能使用创建者的安全上下文。
这种效果,我们就可以通过将SecurityContextHolder配置成MODE_INHERITABLETHREADLOCAL策略达到。
SecurityContext又到底是个什么东西呢?稍后新开一遍学习记录。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。