100 spring-security 中 /oauth/token 发送请求不携带参数 报错 “401 Unauthorized“

news/2024/4/13 11:38:33/文章来源:https://blog.csdn.net/u011039332/article/details/124914948

前言

最近存在这样的一个问题, 大致的复现方式是 访问 /oauth/token 接口, 然后不携带任何参数, 结果 服务器抛出了一个 "401 Unauthorized" 

针对这个 401, 这里 梳理一下这个流程, 也会衍生出一些其他的问题 

 

 

测试用例

客户端这边大致的情况是 构造参数, 然后发送请求, 上面两个 参数封装到查询字符串的请求是可以正常处理 

后面两个 未携带参数 的请求发送, 报错 "401 Unauthorized" 

我们这里主要关心 这里的未携带参数 的异常场景, 以及 造成这个问题的整个流程 

3. 具体的 ignore-urls 的业务层面的使用

主要是在 FilterSecurityInterceptor, 这里面我们核心需要关注几个部分

    3.1 this.obtainSecurityMetadataSource().getAttributes(object) 根据当前 request 获取认证属性

    3.2 Authentication authenticated = authenticateIfRequired(); 根据请求头里面拿到的 token, 访问 auth 服务, 获取用户以及关联信息, 封装 Authentication

    3.3 accessDecisionManager.decide 结合 认证属性 和 Authentication 来判断是否有权限

 

/*** Test11TestRestClient** @author Jerry.X.He* @version 1.0* @date 2022/5/19 17:44*/
public class Test11TestRestClient {/// Test11TestRestClientpublic static void main(String[] args) {String username = "xxx";String password = "xxx";String grantType = "password";Map<String, String> queryParams = new LinkedHashMap<>();queryParams.put("username", username);queryParams.put("password", password);queryParams.put("grant_type", grantType);queryParams.put("scope", "server");queryParams.put("client_id", "xxx");queryParams.put("client_secret", "xxx");RestTemplate restTemplate = new RestTemplate();String queryString = Tools.encapQueryString(queryParams);//        String loginUrl = "http://10.60.50.50:9999/auth/oauth/token?" + queryString;
//        Map result = restTemplate.postForObject(loginUrl, null, Map.class);
//        Map result = restTemplate.getForObject(loginUrl, Map.class);String loginUrl = "http://10.60.50.50:9999/auth/oauth/token";Map result = restTemplate.postForObject(loginUrl, null, Map.class);// todo, getForObject 的时候 查询字符串 未拼接 到 uri 中
//        Map result = restTemplate.getForObject(loginUrl, Map.class, queryParams);int x = 0;}}

 

 

问题的调试

来到认证被拒绝的地方, 可以看到的是 /oauth/token  请求是需要 fullyAuthenticated 的 

cffec01b9c7a41cca0c410587e6d16ca.png

 

 

fullyAuthenticated  的条件是 不是匿名用户 并且 xxx, 这里我们的 authentication 因为没有携带参数信息, 然后 匿名过滤器填充了一个 匿名的 authentication 

83da41b181384cc08113d50beeb5a87a.png

 

 

假设是在客户端正常携带了参数的场景下面, 是怎么过验证的呢? 

这里走的是 ClientCredentialsTokenEndpointFilter 走的一个 自定义的处理, 获取认证信息, 这里能够正常的拿到认证信息, 因此能够正常认证通过 

0c75731afd854139befc922551783293.png

 

 

我准备临时处理一下, 去掉 /oauth/token 的需要认证的处理, 于是在 WebSecurityConfigurer 中增加了 antMatchers("/oauth/token").permitAll() 但是, 从 this.obtainSecurityMetadataSource().getAttributes(object) 拿到的 attributes 还是 fullyAuthenticated, 而不是我这里配置的 permitAll 

我们这里会出现几点疑问 

  1. 为什么 /oauth/token 配置的是 fullyAuthenticated 
  2. 为什么 我配置了 antMatchers("/oauth/token").permitAll(), 但是没有生效 

 

 

为什么 /oauth/token 配置的是 fullyAuthenticated 

AuthorizationServerSecurityConfiguration 中 init 的时候初始化的时候配置 /oauth/token 为 fullyAuthenticated 

AuthorizationServerSecurityConfiguration  是继承自 SecurityConfigurer, 是 spring-security 中自带的 XXConfiguration, 业务上面的限定 一般还存在一个 WebSecurityConfigurer 

b296d8244a68429aaab8d4d73616d200.png

 

 

为什么 我配置了 antMatchers("/oauth/token").permitAll(), 但是没有生效 

这里 AuthorizationServerSecurityConfiguration 的限定的是 "/oauth/token", "/oauth/token_key", "/oauth/check_token" 
WebSecurityConfigurer 限定的是 其他的业务上的大部分的请求, 我这里添加了一个 /oauth/token -> permitAll 但是没有生效 

这里需要 回溯一下 FilterSecurityInterceptor 的这一系列的整个流程 

这里 正向剖析一下这个流程, 反向回溯的过程 着实开销不小 

 

WebSecurityConfiguration.springSecurityFilterChain 创建了一个 FilterChainProxy, 注册于 spring 容器中 

并且添加到了 tomcat 的 applicationFilterChain, 因此和具体的 http 请求处理 的 TokenEndpoint 处理的过程关联起来 

向 tomcat 的 applicationFilterChain 添加 filterDef 的地方来自于 SecurityFilterAutoConfiguration.securityFilterChainRegistration, 增加了一个 DelegatingFilterProxyRegistrationBean 

 

 

这里 FilterChainProxy 内部组合了三个 filterChain, 然后根据 request 获取实际处理的 filterChain, 构建 filter 责任链来处理 请求, 构建了一个 VirtualFilterChain, 在 tomcat 的 filter 责任链的基础上, 又嵌套了一层 filter责任链 

这里 我们的 /oauth/token, 匹配到了 第二个 filterChain, oauth/token 对应的策略为 fullyAuthenticated 

第一个 filterChain 对应的是 WebSecurityConfigurer 中 构建的一个 ignoring 的策略 

第二个 filterChain 对应的是 AuthorizationServerSecurityConfiguration 构建的一个 filterChain , 其中 /oauth/token 对应的策略为 fullyAuthenticated 

第三个 filterChain 对应的是 业务系统中自定义的 WebSecurityConfigurer 构建的一个 filterChain, 其中 /oauth/token 对应的策略为 permitAll 

构建这个 FilterChainProxy 的过程是在 WebSecurity. performBuild 的过程中, 先添加的 ignoring, 然后再添加的 各个 WebSecurityConfigurer 构建的 securityFilterChainBuilder 

55a57b15ef0044649d9feefb3b532e9b.png

 

 

梳理一下构建 FilterChainProxy 的整个流程 

  1. WebSecurityConfiguration.springSecurityFilterChain 中创建了 WebSecurity, WebSecurity.build 创建了 tomcat 这边接入的 FilterChainProxy 
  2. 这个 WebSecurity 组合了 applicationContext 中的 SecurityConfigurer, SecurityConfigurer 生成一个 HttpSecurity, HttpSecurity.build 会创建一个 DefaultFilterChain, 对应于 FilterChainProxy 组合的多个 DefaultFilterChain

 

  1. 在 WebWecurity init 的过程中, 各个 SecurityConfigurer 构建了相应的 HttpSecurity 然后注册到 WebWecurity 中, 这个过程会回调 SecurityConfigurer.configure(HttpSecurity http) 
  2. 在 WebWecurity configure 的过程中, 会回调各个 SecurityConfigurer 的 configure(WebSecurity web) 
  3. 在 WebWecurity performBuild 的过程中, 会构建 FilterChainProxy, 它组合了一系列的 DefaultFilterChain, 这个过程参见 WebWecurity.performBuild, 具体的方式是 组合 WebWecurity 下面的 HttpSecurity 来构建 DefaultFilterChain

            3.1 HttpSecurity performBuild 的过程中, 是直接根据已经采集的 filter 列表, 构建 DefaultFilterChain

            3.2 HttpSecurity  的已经采集的 filter 是来自于 HttpSecurity 下面的 configurers 在 init, configure 的过程中处理进去的 

            3.3 HttpSecurity 下面的各个 configurers 来自于 SecurityConfigurer 的 configure(HttpSecurity http) 的过程中的相关业务处理, 诸如 '.authorizeRequests().antMatchers("/token/**", "/actuator/**", "/sso/**", "/test/**", "/oauth/token").permitAll()', '.formLogin().loginPage("/token/login").loginProcessingUrl("/token/form")', ".authorizeRequests()" 的相关是配置的就是请求认证的相关配置 

 

 

以上一系列流程主要的目的是 为 FilterSecurityInterceptor 中的 this.obtainSecurityMetadataSource().getAttributes(object) 服务 

  1. 请求来了之后, 走 FilterChainProxy, FilterChainProxy  选择当前 request 需要使用的 DefaultFilterChain 
  2. DefaultFilterChain 中组合了一个责任链, 其中 FilterSecurityInterceptor 包含了如下认证步骤, 也是认证的相关问题核心需要关注的地方 

引用自 "20220321_01 配置了 security.oauth2.client.ignore-urls 但是访问配置的服务依然 "用户信息已过期或已更新,请重新登录" 问题"

3. 具体的 ignore-urls 的业务层面的使用

主要是在 FilterSecurityInterceptor, 这里面我们核心需要关注几个部分

    3.1 this.obtainSecurityMetadataSource().getAttributes(object) 根据当前 request 获取认证属性

    3.2 Authentication authenticated = authenticateIfRequired(); 根据请求头里面拿到的 token, 访问 auth 服务, 获取用户以及关联信息, 封装 Authentication

    3.3 accessDecisionManager.decide 结合 认证属性 和 Authentication 来判断是否有权限  

 

另外还有一个细节是 可以看到拿到的 filterChain, 相比于我们 SecurityConfigurer 的 configure(HttpSecurity http) 中配置的处理会多一些

这部分 filter 来自于 SecurityConfigurer.getHttp 的过程中初始化的一部分 Configurer, 诸如 AnonymousAuthenticationFilter, ExceptionTranslationFilter 等等 

 

 

getForObject 的时候 查询字符串 未拼接 到 uri 中

这个是 对于 getForObject 的 uriVariables 的理解存在问题 

这里的 uriVariables 对应于 uri 中的一部分模板, 而不是 我们常规理解的 get 请求中交互的参数 

e6c36ff20673464d8a4d0ed2d6fdff3f.png

 

 

完 

 

 

 

 

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

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

相关文章

arguments和剩余参数(...)

1、arguments对象 是函数内部内置的对象&#xff0c;是一个伪数组&#xff0c;包含了调用函数是传入的所有实参。可用来动态获取函数的实参。 function init(a,b,c) {console.log(arguments)}init(1,2,3) 2、剩余函数(...) 获取多余的实参&#xff0c;并形成一个真数组&#xf…

【邀请码体系设计】准入型邀请码的功能模块设计方案

文章讨论了准入型和营销型邀请码的技术需求&#xff0c;强调了管理功能、配置灵活性、数据分析能力和预生成策略的重要性。准入型邀请码的属性包括唯一标识符、生成任务ID、有效时间、使用方式和状态&#xff0c;这些特点确保了其在一次性使用场景中的有效管理和追踪。 文章目录…

Spring——Bean的作用域

bean的作用域 Bean Scope Scope说明singleton&#xff08;默认情况下&#xff09;为每个Spring IoC容器将单个Bean定义的Scope扩大到单个对象实例。prototype将单个Bean定义的Scope扩大到任何数量的对象实例。session将单个Bean定义的Scope扩大到一个HTTP Session 的生命周期…

kibana配置 dashbord,做可视化展示

一、环境介绍 这里我使用的kibana版本为7.17版本。 语言选择为中文。 需要已经有es&#xff0c;已经有kibana&#xff0c;并且都能正常访问。 二、背景介绍 kibana的可视化界面&#xff0c;可以配置很多监控统计界面。非常方便&#xff0c;做数据的可视化展示。 这篇文章&…

Rust错误处理和Result枚举类异常错误传递

Rust 有一套独特的处理异常情况的机制&#xff0c;它并不像其它语言中的 try 机制那样简单。 首先&#xff0c;程序中一般会出现两种错误&#xff1a;可恢复错误和不可恢复错误。 可恢复错误的典型案例是文件访问错误&#xff0c;如果访问一个文件失败&#xff0c;有可能是因…

修改Android打包apk的名字和目录

app打包生成apk后通常需要进行备份&#xff0c;但是要区分好哪个apk是什么版本的、什么时候打包的&#xff0c;以方便以后区分使用。 最开始的想法是把版本号、创建时间这些加在apk文件名上即可&#xff0c;但是公司要求apk使用一个固定的名称&#xff0c;那我怎么保存版本号信…

SICP解读指南:深度阅读 “计算机领域三巨头” 之一(文末送书)

&#x1f308;个人主页&#xff1a;聆风吟_ &#x1f525;系列专栏&#xff1a;Linux实践室、网络奇遇记 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 &#x1f4cb;前言一. 书籍介绍1.1 SICP侧重点1.2 SICP章节介绍 二. 书籍推荐2.1 书籍介绍2.2 推…

LVS 负载均衡 - DR模式

一 . DR 模式 直接路由 1.介绍&#xff1a; 直接路由&#xff08;Direct Routing&#xff09;&#xff1a;简称 DR 模式&#xff0c;采用半开放式的网络结构&#xff0c;与 TUN 模式的结构类似&#xff0c;但各节点并不是分散在各地&#xff0c;而是与调度器位于同一个物…

大语言模型系列-GPT-3.5(ChatGPT)

文章目录 前言一、GPT-3.5的创新点二、GPT-3.5的训练流程SFT数据集RM数据集PPO数据集 三、ChatGPT的诞生总结 前言 《Training language models to follow instructions with human feedback&#xff0c;2022》 前文提到了GPT-3的缺点&#xff0c;其中最大的问题是&#xff1…

JuiceSSH结合Cpolar实现公网远程SSH访问内网Linux系统

文章目录 1. Linux安装cpolar2. 创建公网SSH连接地址3. JuiceSSH公网远程连接4. 固定连接SSH公网地址5. SSH固定地址连接测试 处于内网的虚拟机如何被外网访问呢?如何手机就能访问虚拟机呢? cpolarJuiceSSH 实现手机端远程连接Linux虚拟机(内网穿透,手机端连接Linux虚拟机) …

JavaScript 中的类型转换机制(详细讲解)

文章目录 一、概述二、显示转换Number()parseInt()String()Boolean() 三、隐式转换自动转换为布尔值自动转换成字符串自动转换成数值 一、概述 前面我们讲到&#xff0c;JS中有六种简单数据类型&#xff1a;undefined、null、boolean、string、number、symbol&#xff0c;以及…

【C++】C++入门—初识构造函数 , 析构函数,拷贝构造函数,赋值运算符重载

C入门 六个默认成员函数1 构造函数语法特性 2 析构函数语法特性 3 拷贝构造函数特性 4 赋值运算符重载运算符重载赋值运算符重载特例&#xff1a;前置 与 后置前置&#xff1a;返回1之后的结果后置&#xff1a; Thanks♪(&#xff65;ω&#xff65;)&#xff89;谢谢阅读&…

Docker-部署若依项目

文章目录 后端一、搭建局域网二、redis安装测试 三、MySQL安装四、后端项目放入位置及使用Dockerfile自定义镜像后端项目放入位置 前端配置检查各个端口是否启动nginx部署 首先得先把内部的文件给删除清空 docker images–查看有哪些文件 docker rmi -f ID–删除ID 后端 一、…

根据xlsx文件第一列的网址爬虫

seleniumXpath 在与该ipynb文件同文件下新增一个111.xlsx&#xff0c;第一列放一堆需要爬虫的同样式网页 然后使用seleniumXpath爬虫 from selenium import webdriver from selenium.webdriver.common.by import By import openpyxl import timedef crawl_data(driver, url)…

原生IP是什么?如何获取海外原生IP?

一、什么是原生IP 原生IP地址是互联网服务提供商&#xff08;ISP&#xff09;直接分配给用户的真实IP地址&#xff0c;无需代理或转发。这类IP的注册国家与IP所在服务器的注册地相符。这种IP地址直接与用户的设备或网络关联&#xff0c;不会被任何中间服务器或代理转发或隐藏。…

代码随想录算法训练营第11天

20. 有效的括号 方法&#xff1a; 1. 如果 !st.empty() return false2.如果st.top() ! s[i] return false3. 如果 st.empty() return false注意&#xff1a; 以下这种写法 不满足 题目要求的第二点&#xff0c;不能以正确的顺序闭合 if(s[i] st.top()){return true;s…

[linux] 使用 kprobe 观察 tcp 拥塞窗口的变化

tcp 中拥塞窗口用来做拥塞控制。 在发送侧&#xff0c;要发送数据的时候会基于拥塞窗口进行判断&#xff0c;当前这个包还能不能发送出去。 tcp 发包函数是 tcp_write_xmit()&#xff0c;在这个函数中调用 tcp_cwnd_test() 来判断当前拥塞窗口让不让发包。从 tcp_cwnd_test() 函…

马士超:符合国际标准的沉浸式音频HOLOSOUND的发展与未来 | 演讲嘉宾公布

一、3D音频 3D 音频分论坛将于3月27日同期举办&#xff01; 3D音频技术不仅能够提供更加真实、沉浸的虚拟世界体验&#xff0c;跨越时空的限制&#xff0c;探索未知的世界。同时&#xff0c;提供更加丰富、立体的情感表达和交流方式&#xff0c;让人类能够更加深入地理解彼此&a…

网络安全: Kali Linux 使用 MSF 漏洞利用

目录 一、实验 1.环境 2.POC验证与nmap扫描&#xff08; ms15-034 &#xff09; 3. Kali Linux 使用 MSF 漏洞利用&#xff08; ms15-034 &#xff09; 4.Windows server 安全加固 5.Windows server 安装补丁 6. Kali Linux 使用 MSF 漏洞验证 &#xff08; ms17-010&…

【力扣 - 无重复字符的最长字符串】

题目描述 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长子串 的长度。 示例 1: 输入: s "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc"&#xff0c;所以其长度为 3。 示例 2: 输入: s "bbbbb" 输出: 1 …