spring security 如何在子线程中获取父线程中的用户认证信息(更改安全策略)

您所在的位置:网站首页 怎么从线程池中取出一个线程 spring security 如何在子线程中获取父线程中的用户认证信息(更改安全策略)

spring security 如何在子线程中获取父线程中的用户认证信息(更改安全策略)

2023-08-16 23:32| 来源: 网络整理| 查看: 265

背景

因为我们的代码中部分操作会有权限审计,在开发过程中,又经常会用到异步或者多线程,就会发现用户明明登录了,但是子线程却读不到用户信息。

简单看了下spring security的源码,发现有以下直接向ThreadLocal中添加Authentication对象、更改spring security安全策略、手动向ThreadLocal中添加权限校验对象绕过检验三个解决办法,其中前面两个方法用起来较简单。

以下代码是我工作中使用到的一个静态工具类,也用于下面的测试。

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中的内容来解决如题说的问题。

直接向ThreadLocal中添加Authentication对象

代码如下:

private static final ForkJoinPool FORK_JOIN_POOL = new ForkJoinPool(Runtime.getRuntime().availableProcessors() + 4); Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); List 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安全策略

关于这个方法,直接截取源码中的一段描述:

spring security支持三种安全策略,MODE_THREADLOCAL、MODE_INHERITABLETHREADLOCAL、MODE_GLOBAL。如果没有指定,则会默认使用MODE_THREADLOCAL策略。

有两种方式来指定strategy,第一种是通过设置JVM参数 -Dspring.security.strategy=MODE_INHERITABLETHREADLOCAL;

第二种是在项目启动的时候调用org.springframework.security.core.context.SecurityContextHolder#setStrategyName方法。

三种策略的解释如下,至于各个策略的具体实现原理,下面看源码就知道了:

MODE_THREADLOCAL表示用户信息只能由当前线程访问。

MODE_INHERITABLETHREADLOCAL策略表示用户信息可以由当前线程及其子线程访问.

MODE_GLOBAL这个策略表示用户信息没有线程限制,全局都可以访问,一般用于gui的开发中,这里可以忽略。

源码

接下来看下源码,源码主要涉及到org.springframework.security.core.context.SecurityContextHolder、org.springframework.security.core.context.SecurityContextHolderStrategy两个类,以SecurityContextHolder为入口,毕竟拿SecurityContext都是从它那里拿的。

SecurityContextHolder在内部维护了一个SecurityContextHolderStrategy实例,并在这个实例的基础上提供了一系列的静态方法。其功能只有两个,一个是为了很方便的给JVM指定相应的strategy,一个是对外提供securityContext的读取设置接口。

从以上可以看出,SecurityContextHolder类的核心在于SecurityContextHolderStrategy,而SecurityContextHolderStrategy接口的三个实现类就是通过不同类型的静态常量contextHolder用来保存SecurityContext的,SecurityContext中含有当前正在访问系统的用户的详细信息。

默认情况下,使用的org.springframework.security.core.context.ThreadLocalSecurityContextHolderStrategy实现类使用ThreadLocal来保存SecurityContext,这也就意味着我们只能在同一线程中从ThreadLocal获取到当前的SecurityContext。

补充一点,因为线程池中的线程会复用,如果每次使用之后线程中的用户信息没有清除,那么就有可能出现用户信息错乱的情况,好在这些工作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的值(父子线程引用同一个对象).

手动向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); }


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3