【深入浅出 Spring Security(四)】登录用户数据的获取,超详细的源码分析

这篇具有很好参考价值的文章主要介绍了【深入浅出 Spring Security(四)】登录用户数据的获取,超详细的源码分析。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

在【深入浅出Spring Security(一)】Spring Security的整体架构 中叙述过一个SecurityContextHolder 这个类。说在处理请求时,Spring Security 会先从 Session 中取出用户登录数据,保存到 SecurityContextHolder 中,然后在请求处理完毕后,又会拿 SecurityContextHolder 中的数据保存到 Session 中,然后再清空 SecurityContextHolder 中的数据。且说了 SecurityContextHolder 内部数据保存默认是通过 ThreadLocal 来实现的。

下面分析 SecurityContextHolder 的源码,并述说如何在代码中获取登录用户的数据。
(如果不想看源码分析的可以直接跳过看怎么获取用户数据)

一、SecurityContextHolder 源码分析

在分析源码之前,可以看一下下面这个图,它展示了 SecurityContextHolder 和 用户数据信息 的结构关系。SecurityContextHolder 依赖 SecurityContext,SecurityContext 封装了 Authentication,而Authentication 即是我们所指的认证后的用户数据信息。(从这关系以及上面的分析,大概应该可以猜测到 SecurityContextHolder 中用了策略设计模式,命名也都很规范化,~ Context,~ ContextHolder🤣)
【深入浅出 Spring Security(四)】登录用户数据的获取,超详细的源码分析

策略模式(Strategy):它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。

既然说它用了策略模式,那SecurityContextHolder中定义的算法家族呢?下面来看一下SecurityContextHolder类中的属性。

public class SecurityContextHolder {
	// 指的是算法策略中的ThreadLocalSecurityContextHolderStrategy
	public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";
	//InheritableThreadLocalSecurityContextHolderStrategy
	public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";
	// GlobalSecurityContextHolderStrategy
	public static final String MODE_GLOBAL = "MODE_GLOBAL";
	// 这个表示不适用任何策略,用原先的HttpSession
	private static final String MODE_PRE_INITIALIZED = "MODE_PRE_INITIALIZED";
	// 配置名称
	public static final String SYSTEM_PROPERTY = "spring.security.strategy";
	// 首先是从系统配置中获取
	// idea中可以在vmoptions中进行配置,
	// 例如:-Dspring.security.strategy=MODE_THREADLOCAL
	private static String strategyName = System.getProperty(SYSTEM_PROPERTY);

	private static SecurityContextHolderStrategy strategy;
}

可以看见有一个 SecurityContextHolderStrategy 对象 strategy,它就是“算法的封装体”。SecurityContextHolderStrategy 是一个接口,下面是其源代码,比较简单。

public interface SecurityContextHolderStrategy {
	// 清除SecurityContext
	void clearContext();
	// 获取SecurityContext
	SecurityContext getContext();
	// 存取SecurityContext
	void setContext(SecurityContext context);
	// 得到一个空的SecurityContext
	SecurityContext createEmptyContext();

}

看下图可以知道算法家族的成员。

【深入浅出 Spring Security(四)】登录用户数据的获取,超详细的源码分析

  • ThreadLocalSecurityContextHolderStrategy:存储数据的载体是一个 ThreadLocal,所以针对 SecurityContext 的清空、获取以及存储,都是在 ThreadLocal 中进行操作。源码过于简单,不分析了,自己看吧。
    【深入浅出 Spring Security(四)】登录用户数据的获取,超详细的源码分析

  • InheritableThreadLocalSecurityContextHolderStrategy:和前者实现策略没有区别,只不过用的是ThreadLocal的子类InheritableThreadLocal,这样子线程和父线程都可以获取到用户数据了。源码也没啥,自己看看就OK了。
    【深入浅出 Spring Security(四)】登录用户数据的获取,超详细的源码分析

  • GlobalSecurityContextHolderStrategy:它实现起来就更更更简单了,直接用个静态变量保存 SecurityContext,所以多线程环境下它是可以使用了,但一般在web开发中,这肯定是使用的少的。
    【深入浅出 Spring Security(四)】登录用户数据的获取,超详细的源码分析

  • ListeningSecurityContextHolderStrategy:SecurityContext 的事件监听策略,它是5.6版本后推出来放到SecurityContextHolderStrategy 策略中的。《深入浅出 Spring Security》书中并没有提到它,但我还是有必要了解的。使用它可以在不去配置系统配置的情况下更换策略,也可以监听 SecurityContext 的创建和销毁事件。注意这里没有获取事件。

    • 它构造方法进行了重载,可以看一下(有些构造源码上说5.7更新的,不管了,现在都 6点 多了),一些判断是否为空的代码我就去调了,留核心代码。
public final class ListeningSecurityContextHolderStrategy implements SecurityContextHolderStrategy {

	// 监听器集合
	private final Collection<SecurityContextChangedListener> listeners;
	// 委托策略对象,默认的话也是ThreadLocalSecurityContextHolderStrategy
	private final SecurityContextHolderStrategy delegate;

	public ListeningSecurityContextHolderStrategy(Collection<SecurityContextChangedListener> listeners) {
		this(new ThreadLocalSecurityContextHolderStrategy(), listeners);
	}


	public ListeningSecurityContextHolderStrategy(SecurityContextChangedListener... listeners) {
		this(new ThreadLocalSecurityContextHolderStrategy(), listeners);
	}


	public ListeningSecurityContextHolderStrategy(SecurityContextHolderStrategy delegate,
			Collection<SecurityContextChangedListener> listeners) {
		this.delegate = delegate;
		this.listeners = listeners;
	}

// 可变参数重载,可进行配置自己想要的策略对象(delegate)
	public ListeningSecurityContextHolderStrategy(SecurityContextHolderStrategy delegate,
			SecurityContextChangedListener... listeners) {
		this.delegate = delegate;
		this.listeners = Arrays.asList(listeners);
	}

在看看它的其他源码,在创建和销毁 SecurityContext 的时候会调用监听器去监听。


	@Override
	public void clearContext() {
		SecurityContext from = getContext();
		this.delegate.clearContext();
		publish(from, null);
	}
	
	@Override
	public SecurityContext getContext() {
		return this.delegate.getContext();
	}

	@Override
	public void setContext(SecurityContext context) {
		SecurityContext from = getContext();
		this.delegate.setContext(context);
		publish(from, context);
	}

	@Override
	public SecurityContext createEmptyContext() {
		return this.delegate.createEmptyContext();
	}
	// 执行监听措施
	private void publish(SecurityContext previous, SecurityContext current) {
		if (previous == current) {
			return;
		}
		SecurityContextChangedEvent event = new SecurityContextChangedEvent(previous, current);
		for (SecurityContextChangedListener listener : this.listeners) {
			listener.securityContextChanged(event);
		}
	}

监听器SecurityContextChangedEvent 是一个函数式接口,咱配置的时候直接使用 lambda 就好了。

讲了半天的策略,回归策略的封装者 SecurityContextHolder。来看看它的初始化操作,它是提供了一个静态代码块,执行初始化。

	static {
		initialize();
	}

	private static void initialize() {
		initializeStrategy();
		initializeCount++;
	}

	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");
			return;
		}
		// 然后判断是否为空,为空就默认设置为ThreadLocalSecurity...
		if (!StringUtils.hasText(strategyName)) {
			// Set default
			strategyName = MODE_THREADLOCAL;
		}
		// 这后面就一系列的判断没啥。
		if (strategyName.equals(MODE_THREADLOCAL)) {
			strategy = new ThreadLocalSecurityContextHolderStrategy();
			return;
		}
		if (strategyName.equals(MODE_INHERITABLETHREADLOCAL)) {
			strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
			return;
		}
		if (strategyName.equals(MODE_GLOBAL)) {
			strategy = new GlobalSecurityContextHolderStrategy();
			return;
		}
		// 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);
		}
	}

了解了其如何进行初始化的后,那就好办了,直接看它内部方法吧。其内部方法都是静态的。

// 清除SecurityContext
public static void clearContext() {
		strategy.clearContext();
	}
// 获取SecurityContext
	public static SecurityContext getContext() {
		return strategy.getContext();
	}

// 初始化次数,emmm,发送请求的次数?
	public static int getInitializeCount() {
		return initializeCount;
	}

// 配置SecurityContext
	public static void setContext(SecurityContext context) {
		strategy.setContext(context);
	}

// 配置StrategyName
	public static void setStrategyName(String strategyName) {
		SecurityContextHolder.strategyName = strategyName;
		initialize();
	}
// 出于5.6版本,估计是让你更好的配置监听策略用的,事实上也就这个方法可以做到了
	public static void setContextHolderStrategy(SecurityContextHolderStrategy strategy) {
		Assert.notNull(strategy, "securityContextHolderStrategy cannot be null");
		SecurityContextHolder.strategyName = MODE_PRE_INITIALIZED;
		SecurityContextHolder.strategy = strategy;
		initialize();
	}
// 获取策略对象
	public static SecurityContextHolderStrategy getContextHolderStrategy() {
		return strategy;
	}

// 创建空的SecurityContext
// 也就是创建SecurityContextImpl
	public static SecurityContext createEmptyContext() {
		return strategy.createEmptyContext();
	}

源码分析到这,差不多就很清晰了,再看看SecurityContextImpl的源码吧,其实不用看也知道,就是 Authentication 对象的封装,这看一下属性和构造就差不多可以猜到大概了。【深入浅出 Spring Security(四)】登录用户数据的获取,超详细的源码分析
最后还需要注意:ThreadLocalSecurityContextHolderStrategy、InheritableThreadLocalSecurityContextHolderStrategy、GlobalSecurityContextHolderStrategy 访问权限都是default默认的,不是本包下的是不让new的,也就是对外不让实例化,你只能通过它给的进行对内策略更改。

ListeningSecurityContextHolderStrategy 使用案例

@Component
@Slf4j
public class InitCommandRun implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
// 配置 InheritableThreadLocalSecurityContextHolderStrategy        
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
// 去获取这个策略对象
        SecurityContextHolderStrategy initStrategy = SecurityContextHolder.getContextHolderStrategy();
        // 将获取到的策略对象用到监听策略中,当委托策略
        SecurityContextHolderStrategy strategy = new ListeningSecurityContextHolderStrategy(
                initStrategy,
                event -> {
            if(event.getNewContext() != null)
                log.warn("new context->{}",event.getNewContext());
        });
        SecurityContextHolder.setContextHolderStrategy(strategy);
    }
}

测试结果

【深入浅出 Spring Security(四)】登录用户数据的获取,超详细的源码分析

SecurityContextPersistenceFilter 说明

Persistence(持久性)。

在【深入浅出Spring Security(二)】Spring Security的实现原理 中概述了Spring Security 中默认加载的过滤器,SecurityContextPersistenceFilter 即是其中的一员。它的作用是为了存储 SecurityContext 而设计的。

它整体来说做了两件事:

  • 当一个请求到来时,从 HttpSession 中获取 SecurityContext 并存入 SecurityContextHolder 中,这样在同一个请求的后续处理过程中,开发者始终可以通过 SecurityContextHolder 获取到当前登录用户信息。
  • 当一个请求处理完毕时,从 SecurityContextHolder 中获取 SecurityContext 并存入 HttpSession 中(主要针对异步 Servlet,不是异步的相应提交自动就会保存到HttpSession中),方便下一个请求到来时,再从 HttpSession 中拿出来使用,同时擦除 SecurityContextHolder 中的登录用户信息。

下面是 SecurityContextPersistenceFilter 过滤器的核心代码(下面出现的 repo 是 SecurityContextRepository 对象,默认是HttpSessionSecurityContextRepository对象):

	private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		// 获取 SecurityContext 对象
		HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
		SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
		try {
		// 存入到 SecurityContextHolder 中
			SecurityContextHolder.setContext(contextBeforeChainExecution);
			// 让下一个过滤器处理请求
			chain.doFilter(holder.getRequest(), holder.getResponse());
		}
		finally {
		// 请求结束后清楚SecurityContextHolder 中的用户信息
		// 并把信息保存在HttpSession中
			SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
			// Crucial removal of SecurityContextHolder contents before anything else.
			SecurityContextHolder.clearContext();
			this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
			request.removeAttribute(FILTER_APPLIED);
		}
	}

注意:SecurityContextPersistenceFilter被标记为已过时(Deprecated),但它仍然被包含在Spring Security默认的过滤器链中。这是因为虽然存在一些问题,但它仍然是一个广泛使用的过滤器,并且在某些情况下仍然是有用的。新版本是去拿 PersistentTokenBasedRememberMeServices 去取代它。

二、登录用户数据的获取

通过上面的源码分析呢?咱可以知道如何获取用户信息了(Authentication)。
调用 SecurityContextHolder 中的 getContext() 静态方法获取其对应策略中保存的 SecurityContext 对象,再调用 getAuthentication() 方法获取 Authentication 对象。

@RestController
public class TestController {

    @GetMapping("/test")
    public Object test(){
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        User user = (User) authentication.getPrincipal();
        /*return "Spring Security Test Success!";*/
        return user;
    }

}

getPrincipal() 是去获取主要的用户信息,它是一个User对象,所以可以进行强转。

测试效果

【深入浅出 Spring Security(四)】登录用户数据的获取,超详细的源码分析
由于我做了如下配置,所以即使在多线程情况下,也是可以使用的(子线程可以用父线程中的 SecurityContext)。

【深入浅出 Spring Security(四)】登录用户数据的获取,超详细的源码分析

三、总结

  • SecurityContextPersistenceFilter 完成了 SecurityContext 的存储和擦除;
  • 在 5.6 版本(准确来说是5.7)后引入了 ListeningSecurityContextHolderStrategy 监听SecurityContext策略;
  • 可以使用 SecurityContextHolder.getContext.getAuthentication() 的方式获取登录用户数据;
  • SecurityContext 的存储和擦除内部用了策略设计模式,SecurityContextHolder 中定义了 SecurityContextHolderStrategy 策略,去获取、擦除、存储SecurityContext。

当使用 ListeningSecurityContextHolderStrategy 时,可以向如下这样使用。当然它默认的执行策略是 ThreadLocalSecurity… ,所以当不需要换策略的话直接用监听器对象当构造参数构造即可,如果想切换成多线程,就像如下那样配置吧。

【深入浅出 Spring Security(四)】登录用户数据的获取,超详细的源码分析当然也可以去配置idea的vmoptions参数,但小编并不觉得它是个好主意。你觉得呢?文章来源地址https://www.toymoban.com/news/detail-469520.html

到了这里,关于【深入浅出 Spring Security(四)】登录用户数据的获取,超详细的源码分析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【深入浅出 Spring Security(七)】RememberMe的实现原理详讲

    先看看最简单用法的默认页面效果变化。 SecurityConfig 配置类 测试 TestController 代码 以下是给出的默认的登录页面。 观察页面源代码可以发现,比原先没配置 RememberMe 之前多了个 name 为 remember-me 的 checkbox 选项。 如果我们勾选了它并且登录成功后,当我们关闭掉当前浏览器,

    2024年02月09日
    浏览(27)
  • 【深入浅出 Spring Security(十三)】使用 JWT 进行前后端分离认证(附源码)

    JWT 全称 Java web Token,在此所讲述的是 JWT 用于身份认证,用服务器端生成的JWT去替代原始的Session认证,以提高安全性。 JWT本质是一个Token令牌,是由三部分组成的字符串,分别是头部(header)、载荷(payload)和签名(signature)。头部一般包含该 JWT 的基本信息,例如所使用的

    2024年02月12日
    浏览(29)
  • 【深入浅出 Spring Security(十一)】授权原理分析和持久化URL权限管理

    在 【深入浅出Spring Security(一)】Spring Security的整体架构 中小编解释过授权所用的三大组件,在此再解释说明一下(三大组件具体指:ConfigAttribute、AccessDecisionManager(决策管理器)、AccessDecisionVoter(决策投票器)) ConfigAttribute 在 Spring Security 中,用户请求一个资源(通常是

    2024年02月10日
    浏览(36)
  • 深入浅出Spring AOP

    第1章:引言 大家好,我是小黑,咱们今天要聊的是Java中Spring框架的AOP(面向切面编程)。对于程序员来说,理解AOP对于掌握Spring框架来说是超级关键的。它像是魔法一样,能让咱们在不改变原有代码的情况下,给程序增加各种功能。 AOP不仅仅是一个编程范式,它更是一种思

    2024年01月20日
    浏览(42)
  • 深入浅出 Spring:核心概念和基本用法详解

    个人主页:17_Kevin-CSDN博客 收录专栏;《Java》 在 Java 企业级应用开发中,Spring 框架已经成为了事实上的标准。它提供了一种轻量级的解决方案,使得开发者能够更轻松地构建灵活、可扩展的应用程序。在本文中,我们将探讨 Spring 框架的一些核心概念和基本用法,以此更好地

    2024年03月20日
    浏览(46)
  • Spring5深入浅出篇:Spring与工厂设计模式简介

    轻量级 JavaEE的解决⽅案 spring实际上就是对原有设计模式的一种高度封装和整合 整合设计模式 工厂设计模式 什么是工厂设计模式 当UserServiceImpl发生变化是会影响到userService等相关联的类,在线上环境不利于维护

    2024年01月18日
    浏览(43)
  • Spring Security登录用户数据获取(4)

    登录成功之后,在后续的业务逻辑中,开发者可能还需要获取登录成功的用户对象,如果不使用任何安全管理框架,那么可以将用户信息保存在HttpSession中,以后需要的时候直接从HttpSession中获取数据。在Spring Security中,用户登录信息本质上还是保存在 HttpSession中,但是为了方

    2024年02月03日
    浏览(36)
  • Spring5深入浅出篇:bean的生命周期

    指的是⼀个对象创建、存活、消亡的⼀个完整过程 由Spring负责对象的创建、存活、销毁,了解⽣命周期,有利于我们使⽤好Spring为我们创建的对象 创建阶段 Spring⼯⼚何时创建对象 当bean标签中增加scope=\\\"singleton\\\"时,当你创建对象所有的引用都是第一个对象的内存地址;sigleton:只

    2024年04月12日
    浏览(34)
  • 【深入浅出Spring原理及实战】「源码调试分析」深入源码探索Spring底层框架的的refresh方法所出现的问题和异常

    阅读Spring官方文档,了解Spring框架的基本概念和使用方法。 下载Spring源码,可以从官网或者GitHub上获取。 阅读Spring源码的入口类,了解Spring框架的启动过程和核心组件的加载顺序。 阅读Spring源码中的注释和文档,了解每个类和方法的作用和用法。 调试Spring源码,可以通过

    2023年04月23日
    浏览(30)
  • Spring高手之路14——深入浅出:SPI机制在JDK与Spring Boot中的应用

       SPI ( Service Provider Interface ) 是一种服务发现机制,它允许第三方提供者为核心库或主框架提供实现或扩展。这种设计允许核心库/框架在不修改自身代码的情况下,通过第三方实现来增强功能。 JDK原生的SPI : 定义和发现 : JDK 的 SPI 主要通过在 META-INF/services/ 目录下放置

    2024年02月09日
    浏览(31)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包