首页 > 编程语言 > 详细

spring security 如何在子线程中获取父线程中的用户认证信息

时间:2021-03-31 16:22:23      阅读:169      评论:0      收藏:0      [点我收藏+]

背景

在开发过程中,如果在用户登录的前提下需要使用多线程,且在多线程中需要用到spring security中的用户认证信息时可以通过配置来让子线程可以通过以下几种方式获取到父线程中的用户信息.

intellif.utils.CurUserInfoUtil#getUserInfo:
public static UserInfo getUserInfo() {
		Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
		//SecurityContextHolder.getContext().setAuthentication(authentication);
		if(null == authentication){
			return null;
		}
		return (UserInfo)authentication.getPrincipal();
	}

简单的查看核心类SecurityContextHolder源码,可以看到Authentication对象实质是保存当前线程的ThreadLocal中,这个是默认的实现方式,大部分情况已经够用,另外还有可能存在InheritableThreadLocal或者静态变量中,这个后续再详说,由此可以得到第一个最简单的方法来解决如题说的问题.

直接向ThreadLocal中添加Authentication对象

代码如下:

    private static final ForkJoinPool FORK_JOIN_POOL = new ForkJoinPool(Runtime.getRuntime().availableProcessors() + 4);        
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        List<String> list = new ArrayList<>();
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
        list.add("ddd");
        list.stream().map(t -> FORK_JOIN_POOL.submit(() -> threadAuthenticationTest(authentication,t))).
                collect(Collectors.toList()).forEach(FunctionUtil::waitTillThreadFinish);
    private void threadAuthenticationTest(Authentication authentication,String name) {
        SecurityContextHolder.getContext().setAuthentication(authentication);
        UserInfo userInfo = CurUserInfoUtil.getUserInfo();
        System.out.println("userInfo:" + userInfo.getLogin());
        System.out.println("name:" + name);
    }

直接从父线程中获取到Authentication,然后通过传参到子线程,最后子线程再放入SecurityContext中.

debug在子线程中可以看到ThreadLocal中的Authentication信息,通过CurUserinfoUtil也可以获取到用户信息.

技术分享图片

更改spring security安全策略

? 除开上面的办法,还可以通过配置层面来解决这个问题,下面涉及到一点源码

SecurityContextHolder

? 简单的翻译下源码里面的解释,我觉得这个已经很清楚了

? SecurityContextHolder会给当前线程分配一个指定的SecurityContext.

? 这个类在内部维护了一个SecurityContextHolderStrategy实例,并在这个实例的基础上提供了一系列的静态方法,其目的是为了很方便的给JVM指定相应的strategy.由于类中的变量及方法都是static,所以这个类的相关设置是基于JVM层面的.

? 如果需要指定对应的strategy可以选择以下三种模式之一:MODE_THREADLOCAL MODE_INHERITABLETHREADLOCAL MODE_GLOBAL .或者自己实现org.springframework.security.core.context.SecurityContextHolderStrategy接口,提供一个无参构造,然后将类的全限定名传入即可.

? 有两种方式来指定strategy,第一种是通过设置JVM参数,第二种是在使用这个类之前调用org.springframework.security.core.context.SecurityContextHolder#setStrategyName方法,如果没有指定,则会默认使用MODE_THREADLOCAL模式.

? 从以上可以看出,SecurityContextHolder类的核心在于SecurityContextHolderStrategy,而SecurityContextHolderStrategy就是用来保存SecurityContext的.SecurityContext中含有当前正在访问系统的用户的详细信息.默认情况下,SecurityContextHolder将使用ThreadLocal来保存SecurityContext,这也就意味着在处于同一线程中的方法中我们可以从ThreadLocal中获取到当前的SecurityContext.因为线程池的原因,如果我们每次在请求完成后都将ThreadLocal进行清除的话,那么我们把SecurityContext存放在ThreadLocal中还是比较安全的.这些工作Spring Security已经自动为我们做了,即在每一次request结束后都将清除当前线程的ThreadLocal.

? 所谓的strategy设置本质上就是选择SecurityContextHolderStrategy的不同实现类,spring security默认为我们提供了三种实现:

技术分享图片

三者的区别就是存放SecurityContext对象的位置不同,顾名思义,默认的ThreadLocalSecurityContextHolderStrategy即是放在ThreadLocal中,InheritableThreadLocalSecurityContextHolderStrategy的放在InheritableThreadLocal,GlobalSecurityContextHolderStrategy的通过源码可以看出是其SecurityContext是一个静态常量,即全局共享一个SecurityContext,这个具体也不是很清楚,据说用于C/S结构的客户端.

ThreadLocal和InheritableThreadLocal的区别

InheritableThreadLocal继承了ThreadLocal,与前者的区别是ThreadLocal只能由当前线程访问,但是inheritableThreadLocal中的内容子线程也可以访问,至于实现原理通过查看Thread源码,可以看到在创建Thred对象(init方法)时,如果父线程的InheritableThreadLocal不为空的话,子线程会复制父线程的InheritableThreadLocal的值(父子线程引用同一个对象).

修改jar包启动参数

更改策略很简单,设置一个jvm参数即可-Dspring.security.strategy=MODE_INHERITABLETHREADLOCAL即可,注意这个值不能设为环境变量,实测环境变量无法读取.下面是设置新的策略后debug结果:

技术分享图片

配置参数后initialize方法会选择创建InheritableThreadLocalSecurityContextHolderStrategy.

项目正常启动并登录后,调用接口,可以看到父线程的inheritableThreadLocal中有了Authentication对象,如下:

技术分享图片

进入子线程后,可以看到子线程inheritableThreadLocal也有Authentication对象

技术分享图片

实际调用CurUserInfoUtil方法也能获取到用户信息,至此结束.

调用SecurityContextHolder#setStrategyName方法

传入指定StrategyName即可,注意这个方法必须在项目启动(未使用SecurityContextHolder之前)的时候调用,否则会导致之前的用户Authentication信息获取不到.

手动向ThreadLocal中添加权限校验对象绕过检验

    /**
     * 模拟登录,只是为了绕过日志审计,没有别的作用
     */
    private static void login() {
        UserInfo userInfo = new UserInfo();
        userInfo.setLogin("landian");
        userInfo.setRoleTypeName(RoleTypes.SUPER_ADMIN.getName());
        userInfo.setRoleIds("1");
        userInfo.setPoliceStationId(1L);
        userInfo.setId(1);
        RoleInfo roleInfo = new RoleInfo();
        roleInfo.setId(1L);
        roleInfo.setCnName("超级管理员");
        roleInfo.setName("SUPER_ADMIN");
        roleInfo.setResIds("1,100,200,300,400,500,600,700,800");
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userInfo, null, Collections.singletonList(roleInfo));
        OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(null, usernamePasswordAuthenticationToken);
        Request request = new Request(null, null);
        InetSocketAddress inetSocketAddress = new InetSocketAddress("0.0.0.0", 65535);
        request.setRemoteAddr(inetSocketAddress);
        OAuth2AuthenticationDetails oAuth2AuthenticationDetails = new OAuth2AuthenticationDetails(request);
        oAuth2Authentication.setDetails(oAuth2AuthenticationDetails);
        SecurityContextHolder.getContext().setAuthentication(oAuth2Authentication);
    }

spring security 如何在子线程中获取父线程中的用户认证信息

原文:https://www.cnblogs.com/precedeforetime/p/14601101.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!