写点什么

SecurityContextHolder 之策略模式源码分析

作者:周杰伦本人
  • 2022 年 5 月 03 日
  • 本文字数:4045 字

    阅读完需:约 13 分钟

springsecurity 之 登录用户数据的获取

springsecurity 中,用户登录信息本质是保存到 HttpSession 中,springsecurity 进行封装 获取登录数据有两种思路:


  1. 从 SecurityContextHolder 中获取

  2. 从当前请求对象中获取

从 SecurityContextHolder 中获取

@RestControllerpublic class HelloController {    @GetMapping("/hello")    public void hello() {        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();        System.out.println("authentication.getClass() = " + authentication.getClass());    }}
复制代码


SecurityContextHolder 存放的是 SecurityContext ,SecurityContextHolder 中定义三种不同的数据存储策略,采用了策略模式


  1. MODE_THREADLOCAL :将 SecurityContext 放在 ThreadLocal 中,开启子线程,子线程获取不到用户数据。

  2. MODE_INHERITABLETHREADLOCAL:多线程环境,子线程也能获取到用户数据。

  3. MODE_GLOBAL:数据保存到一个静态变量中,web 开发中很少使用。


SecurityContextHolderStrategy 接口用来规范存储策略中的方法


public interface SecurityContextHolderStrategy {    void clearContext();
SecurityContext getContext();
void setContext(SecurityContext var1);
SecurityContext createEmptyContext();}
复制代码


有三个实现类 对应三个不同的存储策略

ThreadLocalSecurityContextHolderStrategy

//// Source code recreated from a .class file by IntelliJ IDEA// (powered by FernFlower decompiler)//
package org.springframework.security.core.context;
import org.springframework.util.Assert;
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 针对 SecurityContext 的操作都是在 ThreadLocal 中进行操作。SecurityContext 只是个接口,只有一个实现类是 SecurityContextImpl

InheritableThreadLocalSecurityContextHolderStrategy

//// Source code recreated from a .class file by IntelliJ IDEA// (powered by FernFlower decompiler)//
package org.springframework.security.core.context;
import org.springframework.util.Assert;
final class InheritableThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy { private static final ThreadLocal<SecurityContext> contextHolder = new InheritableThreadLocal();
InheritableThreadLocalSecurityContextHolderStrategy() { }
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(); }}
复制代码


存储载体为 InheritableThreadLocal ,InheritableThreadLocal 继承 ThreadLocal,多了一个特性,就是在子线程创建的时间,会自动将父线程的数据复制到子线程中。实现了子线程中能够获取登录数据的功能。

GlobalSecurityContextHolderStrategy

//// Source code recreated from a .class file by IntelliJ IDEA// (powered by FernFlower decompiler)//
package org.springframework.security.core.context;
import org.springframework.util.Assert;
final class GlobalSecurityContextHolderStrategy implements SecurityContextHolderStrategy { private static SecurityContext contextHolder;
GlobalSecurityContextHolderStrategy() { }
public void clearContext() { contextHolder = null; }
public SecurityContext getContext() { if (contextHolder == null) { contextHolder = new SecurityContextImpl(); }
return contextHolder; }
public void setContext(SecurityContext context) { Assert.notNull(context, "Only non-null SecurityContext instances are permitted"); contextHolder = context; }
public SecurityContext createEmptyContext() { return new SecurityContextImpl(); }}
复制代码


存储载体是一个静态变量,也可以在多线程环境下使用,但用的较少。

SecurityContextHolder 源码

//// Source code recreated from a .class file by IntelliJ IDEA// (powered by FernFlower decompiler)//
package org.springframework.security.core.context;
import java.lang.reflect.Constructor;import org.springframework.util.ReflectionUtils;import org.springframework.util.StringUtils;
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"; public static final String SYSTEM_PROPERTY = "spring.security.strategy"; private static String strategyName = System.getProperty("spring.security.strategy"); private static SecurityContextHolderStrategy strategy; private static int initializeCount = 0;
public SecurityContextHolder() { }
public static void clearContext() { strategy.clearContext(); }
public static SecurityContext getContext() { return strategy.getContext(); }
public static int getInitializeCount() { return initializeCount; }
private static void initialize() { 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); } }
++initializeCount; }
public static void setContext(SecurityContext context) { strategy.setContext(context); }
public static void setStrategyName(String strategyName) { SecurityContextHolder.strategyName = strategyName; initialize(); }
public static SecurityContextHolderStrategy getContextHolderStrategy() { return strategy; }
public static SecurityContext createEmptyContext() { return strategy.createEmptyContext(); }
public String toString() { return "SecurityContextHolder[strategy='" + strategyName + "'; initializeCount=" + initializeCount + "]"; }
static { initialize(); }}
复制代码


SecurityContextHolder 定义三个静态常量描述三种不同存储策略,在静态代码块中初始化,根据不同的 strategyName 初始化不同的存储策略,可以调用配置系统变量或者调用 setStrategyName 改变策略。


在默认情况下,从子线程中获取登录数据是获取不到的,因为默认是 MODE_THREADLOCAL 策略。


存储策略在 System.getProperty("spring.security.strategy");加载,可以配置系统变量来修改策略。


在 VM option 参数添加


-Dspring.security.strategy=MODE_INHERITABLETHREADLOCAL


SpringContextHolder 默认将用户信息存储在 ThreadLocal 中,在 SpringBoot 中不同的请求使用不同的线程处理,是怎么获取到用户的信息的呢? 下篇文章揭晓,有懂的可以在评论区留言。

发布于: 刚刚阅读数: 2
用户头像

还未添加个人签名 2020.02.29 加入

还未添加个人简介

评论

发布
暂无评论
SecurityContextHolder之策略模式源码分析_5月月更_周杰伦本人_InfoQ写作社区