Spring Security Oauth资源服务器并发情况下获取用户信息错乱

文章目录

    • Oauth2 资源服务器并发情况下获取用户信息错乱
      • 问题描述
      • 项目配置
      • 源码分析
    • 解决方案
      • 1、修改源码
      • 2,添加新的子类实现,并作为新bean注入

Oauth2 资源服务器并发情况下获取用户信息错乱

问题描述

当用户A与用户B分别持有一个合法的令牌token 访问同一个资源服务器时,会间接性的出现,用户A拿着A的合法token 却获取到了用户B的用户信息,B用户相反而之。

项目配置

security:oauth2:resource:user-info-uri: http://127.0.0.1:8081/user-meprefer-token-info: false

源码分析

资源服务器获取用户信息的主要核心源码类是ResourceServerTokenServices

public interface ResourceServerTokenServices {/*** Load the credentials for the specified access token.** @param accessToken The access token value.* @return The authentication for the access token.* @throws AuthenticationException If the access token is expired* @throws InvalidTokenException if the token isn't valid*/OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException;/*** Retrieve the full access token details from just the value.* * @param accessToken the token value* @return the full access token with client id etc.*/OAuth2AccessToken readAccessToken(String accessToken);}

我们当前使用的便是其子类实现之一的UserInfoTokenServices

	@Overridepublic OAuth2Authentication loadAuthentication(String accessToken)throws AuthenticationException, InvalidTokenException {Map<String, Object> map = getMap(this.userInfoEndpointUrl, accessToken);if (map.containsKey("error")) {if (this.logger.isDebugEnabled()) {this.logger.debug("userinfo returned error: " + map.get("error"));}throw new InvalidTokenException(accessToken);}return extractAuthentication(map);}

在这里出现并发问题的主要是在getmap这个函数

private Map<String, Object> getMap(String path, String accessToken) {if (this.logger.isDebugEnabled()) {this.logger.debug("Getting user info from: " + path);}try {OAuth2RestOperations restTemplate = this.restTemplate;if (restTemplate == null) {BaseOAuth2ProtectedResourceDetails resource = new BaseOAuth2ProtectedResourceDetails();resource.setClientId(this.clientId);restTemplate = new OAuth2RestTemplate(resource);}OAuth2AccessToken existingToken = restTemplate.getOAuth2ClientContext().getAccessToken();if (existingToken == null || !accessToken.equals(existingToken.getValue())) {DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(accessToken);token.setTokenType(this.tokenType);restTemplate.getOAuth2ClientContext().setAccessToken(token);}return restTemplate.getForEntity(path, Map.class).getBody();}catch (Exception ex) {this.logger.warn("Could not fetch user details: " + ex.getClass() + ", "+ ex.getMessage());return Collections.<String, Object>singletonMap("error","Could not fetch user details");}}

在这里通过我的debug 发现问题出现在restTemplate.getOAuth2ClientContext().setAccessToken(token);
设置token时候,出现了并发问题。当我打开了该函数的子类实现,一切问题都烟消云散。
该核心类是OAuth2ClientContext,子类默认实现是DefaultOAuth2ClientContext。

	@Overridepublic OAuth2Authentication loadAuthentication(String accessToken)throws AuthenticationException, InvalidTokenException {Map<String, Object> map = getMap(this.userInfoEndpointUrl, accessToken);if (map.containsKey("error")) {if (this.logger.isDebugEnabled()) {this.logger.debug("userinfo returned error: " + map.get("error"));}throw new InvalidTokenException(accessToken);}return extractAuthentication(map);}

在这里出现并发问题的主要是在getmap这个函数

public class DefaultOAuth2ClientContext implements OAuth2ClientContext, Serializable {private static final long serialVersionUID = 914967629530462926L;private OAuth2AccessToken accessToken;private AccessTokenRequest accessTokenRequest;private Map<String, Object> state = new HashMap<String, Object>();public DefaultOAuth2ClientContext() {this(new DefaultAccessTokenRequest());}public DefaultOAuth2ClientContext(AccessTokenRequest accessTokenRequest) {this.accessTokenRequest = accessTokenRequest;}public DefaultOAuth2ClientContext(OAuth2AccessToken accessToken) {this.accessToken = accessToken;this.accessTokenRequest = new DefaultAccessTokenRequest();}public OAuth2AccessToken getAccessToken() {return accessToken;}public void setAccessToken(OAuth2AccessToken accessToken) {this.accessToken = accessToken;this.accessTokenRequest.setExistingToken(accessToken);}public AccessTokenRequest getAccessTokenRequest() {return accessTokenRequest;}public void setPreservedState(String stateKey, Object preservedState) {state.put(stateKey, preservedState);}public Object removePreservedState(String stateKey) {return state.remove(stateKey);}}

在该子类中,可以看出setAccessToken并没有做并发控制,简而言之是当A用户设置了token准备访问url获取用户信息时候,B用户进来修改了该值变为Btoken,然而A用户线程又获取到CPU,开始访问了url链接,拿着已被修改为B的token 值获取了 B的用户信息。

解决方案

1、修改源码

下载spring security源码,修改DefaultOAuth2ClientContext,源码将线程问题解决,然后打包,上传maven私服,修改自己项目工程的maven依赖

2,添加新的子类实现,并作为新bean注入

@Component
public class WAYZDefaultOAuth2ClientContext implements OAuth2ClientContext, Serializable {private static final long serialVersionUID = 3078781745905248724L;// make accessToken thread local to avoid thread safe issueprivate ThreadLocal<OAuth2AccessToken> accessToken = new ThreadLocal<>();private AccessTokenRequest accessTokenRequest;private Map<String, Object> state = new HashMap<String, Object>();public WAYZDefaultOAuth2ClientContext() {this(new DefaultAccessTokenRequest());}public WAYZDefaultOAuth2ClientContext(AccessTokenRequest accessTokenRequest) {this.accessTokenRequest = accessTokenRequest;}public WAYZDefaultOAuth2ClientContext(OAuth2AccessToken accessToken) {this.accessToken.set(accessToken);this.accessTokenRequest = new DefaultAccessTokenRequest();}public OAuth2AccessToken getAccessToken() {return accessToken.get();}public void setAccessToken(OAuth2AccessToken accessToken) {this.accessToken.set(accessToken);this.accessTokenRequest.setExistingToken(accessToken);}public AccessTokenRequest getAccessTokenRequest() {return accessTokenRequest;}public void setPreservedState(String stateKey, Object preservedState) {state.put(stateKey, preservedState);}public Object removePreservedState(String stateKey) {return state.remove(stateKey);}
}

这种方式的实现,或许好多人并不理解,只是把bean放到容器里,就可以替换之前默认实现吗?难道不需要一个配置类引入之类吗?答案是不需要的

我通过源码debug发现改类主要是被OAuth2RestTemplate使用,而OAuth2RestTemplate却又是被UserInfoRestTemplateFactory工厂创建,而UserInfoRestTemplateFactory的构造创建又是在ResourceServerTokenServicesConfiguration中,在这个类里面,已经将容器里已存在的类型bean做了注入,然后默认实现的自动替换,这也是源码的巧妙之处。

原文地址:https://blog.csdn.net/qq_38226693/article/details/107708554

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/719235.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

FPGA之加法逻辑运算

由于FPGA需要被反复烧写&#xff0c;它实现组合逻辑的基本结构不可能像ASIC 那样通过固定的与非门来完成&#xff0c;而只能采用一种易于反复配置的结构。查找表可以很好地满足这一要求&#xff0c;目前主流FPGA都采用了基于SRAM 工艺的查找表结构。LUT本质上就是一个RAM。它把…

HarmonyOS-卡片页面能力说明和使用动效能力

卡片页面能力说明 开发者可以使用声明式范式开发ArkTS卡片页面。如下卡片页面由DevEco Studio模板自动生成&#xff0c;开发者可以根据自身的业务场景进行调整。 ArkTS卡片具备JS卡片的全量能力&#xff0c;并且新增了动效能力和自定义绘制的能力&#xff0c;支持声明式范式的…

python | 列表,元组,字符串,集合,字典

列表&#xff1a; 可以容纳任意数目不同类型的元素&#xff08;支持我们学过的任意数据类型&#xff09;元素之间有先后顺序用中括号包裹&#xff0c;每个元素用逗号隔开 例如&#xff1a; students [林黛玉,薛宝钗,贾元春,贾探春,史湘云,妙玉,贾迎春,贾惜春,王熙凤,贾巧姐…

汇编语言程序设计实验三 数据串传送和查表程序

实验目的和要求 1、堆栈。堆栈指示器SP和堆栈操作指令PUSH。POP。 2、段寄存器和物理地址计算。 3、查表法和查表指令XLAT。 4、数据串传送程序和数据串传送指令MOVS。STOS及重复前辍REP。 5、循环指令ROL。逻辑与指令AND和清方向位指令CLD。 6、伪操作指令DB。 实验环境…

五步法搞定 BI 业务需求梳理

五步法搞定 BI 业务需求梳理。高手就是把复杂的事情简单化&#xff0c;简单的东西重复做、认真做。 01 五步法是哪五步 第一、明确用户 五步法搞定 BI 业务需求梳理。高手就是把复杂的事情简单化&#xff0c;简单的东西重复做、认真做。 第二、明确指标 在明确需求用户的前…

LeetCode 2125.银行中的激光束数量

银行内部的防盗安全装置已经激活。给你一个下标从 0 开始的二进制字符串数组 bank &#xff0c;表示银行的平面图&#xff0c;这是一个大小为 m x n 的二维矩阵。 bank[i] 表示第 i 行的设备分布&#xff0c;由若干 ‘0’ 和若干 ‘1’ 组成。‘0’ 表示单元格是空的&#xff0…

NERF论文笔记

NeRF:Representing Scene as Neural Radiance Fields for View Synthesis 笔记 摘要 实现了一个任意视角视图生成算法&#xff1a;输入稀疏的场景图像&#xff0c;通过优化连续的Volumetric场景函数实现&#xff1b;用全连接深度网络表达场景&#xff0c;输入是一个连续的5维…

Unity(第二十部)效果 粒子、线条和拖尾

1、粒子系统 粒子系统介绍 Unity 粒子系统是 Unity 引擎中用于创建和控制粒子效果的工具。它可以模拟各种自然现象&#xff0c;如火焰、烟雾、雨滴等&#xff0c;也可以用于创建特效&#xff0c;如魔法光芒、爆炸效果等。 粒子系统组成 在 Unity 中&#xff0c;粒子系统由发射…

用 Dockerfile为镜像添加SSH服务

1、基础镜像ubuntu:18.04 2、替换为国内的安装源 3、安装openssh-server 4、允许root用户远程登陆 5、暴露端口22 6、服务开机自启动 1.创建目录 [rootopenEuler-node1 db]# mkdir sshd_ubuntu 2.创建 Dockerfile、 run.sh 、authorized_keys、vim aliyun.list 文件 [rootop…

网盘拉新如何对接?盘点最靠谱的一手渠道平台

2024网盘行业再次重燃战火。字节旗下产品头条搜索极速版APP、悟空浏览器APP推出对应的网盘功能&#xff0c;刚刚开放了拉新推广&#xff0c;现在是一个不能错过新项目的好时机。 如果你对网盘拉新推广充满热情&#xff0c;千万不要错过星子助推联合字节推出的网盘项目机会。小…

python自动化之如何利用allure生成测试报告

Allure测试报告框架帮助你轻松实现”高大上”报告展示。本文通过示例演示如何从0到1集成Allure测试框架。重点展示了如何将Allure集成到已有的自动化测试工程中、以及如何实现报表的优化展示。Allure非常强大&#xff0c;支持多种语言多种测试框架&#xff0c;无论是Java/Pytho…

成功的交易没有对错,只有逻辑

大部分人将交易失败归咎于心态&#xff0c;但其实我们是否认真思考过自己的基本功是否扎实呢&#xff1f;这篇文章将引导你换个角度看待交易&#xff0c;让你明白自己应该努力的方向。 曾经&#xff0c;你或许认为资金体量小、信息不对称、技术不过关、心态不过硬是阻碍交易发展…

TikTok外贸系统的核心功能及其源代码分享!

随着全球化的不断推进&#xff0c;外贸业务成为越来越多企业的增长动力&#xff0c;TikTok作为一个全球性的社交媒体平台&#xff0c;其用户基数庞大、活跃度高&#xff0c;为外贸业务提供了无限的商机。 为了帮助企业在TikTok上更好地开展外贸业务&#xff0c;TikTok外贸系统…

【DDD】学习笔记-聚合和聚合根:怎样设计聚合?

今天我们来学习聚合&#xff08;Aggregate&#xff09;和聚合根&#xff08;AggregateRoot&#xff09;。 我们先回顾下上一讲&#xff0c;在事件风暴中&#xff0c;我们会根据一些业务操作和行为找出实体&#xff08;Entity&#xff09;或值对象&#xff08;ValueObject&…

47、WEB攻防——通用漏洞Java反序列化EXP生成数据提取组件安全

文章目录 序列化和反序列化的概念&#xff1a; 序列化&#xff1a;把java对象转换成字节流的过程&#xff1b;反序列化&#xff1a;把字节流恢复为java对象的过程。 对象的序列化主要有两种用途&#xff1a; 把对象的字节流永久的保存在硬盘上&#xff0c;通常存放在一个文件…

网络分析工具简介及汇总

文章目录 一、网络分析工具软件是什么二、网络分析工具软件作用三、常见的网络分析工具 一、网络分析工具软件是什么 网络分析工具软件是一种用于捕获、分析和解释网络数据包的工具。它们可以直接从网络接口上捕获数据包&#xff0c;并提供详细的信息和统计数据&#xff0c;以帮…

xss.haozi.me:0x0B

<svg><script>(1)</script>

洛谷 B3620 x 进制转 10 进制

题目描述 给一个小整数 x 和一个 x 进制的数 S。将 S 转为 10 进制数。对于超过十进制的数码&#xff0c;用 A&#xff0c;B&#xff0c;…… 表示。 输入格式 第一行一个整数 x; 第二行一个字符串 S。 输出格式 输出仅包含一个整数&#xff0c;表示答案。 输入输出样例…

【JavaScript】面试手撕浅拷贝

【JavaScript】面试手撕浅拷贝 引入 浅拷贝和深拷贝应该是面试时非常常见的问题了&#xff0c;为了能将这两者说清楚&#xff0c;于是打算用两篇文章分别解释下深浅拷贝。 PS: 我第一次听到拷贝这个词&#xff0c;有种莫名的熟悉感&#xff0c;感觉跟某个英文很相似&#xff…

Linux文本处理三剑客:awk(内置函数详解笔记)

Linux系统中&#xff0c;AWK 是一个非常强大的文本处理工具&#xff0c;它的内置函数使得对文本数据进行处理更加高效和便捷。 本文将介绍 AWK 内置函数的几种主要类型&#xff1a; 算数函数字符串函数时间函数位操作函数其他常用函数 我们将使用一个示例文本文件来演示这些函…