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

news2025/1/17 21:48:36

前言

最近存在这样的一个问题, 大致的复现方式是 访问 /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 {

    /// Test11TestRestClient
    public 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.coloradmin.cn/o/1496577.html

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

相关文章

深入浅出Redis(六):Redis的主从架构与主从复制原理

引言 Redis是一款基于内存、键值对的非关系型数据库&#xff0c;它的性能十分的优秀&#xff0c;但单机节点的Redis还是存在许多不足的功能 单机无法保证高可用性&#xff0c;当单机Redis宕机时&#xff0c;无法继续提供服务&#xff0c;在主从架构 哨兵模式下能够解决无法保…

HCIA-Datacom题库(自己整理分类的)_33_DHCP协议多选【7道题】

1.使用动态主机配置协议DHCP分配IP地址有哪些优点? 可以实现IP地址重复利用 工作量大且不好管理 配置信息发生变化(如DNS),只需要管理员在DHCP服务器上修改,方便统一管理 避免IP地址冲突 2.网络中部署了一台DHCP服务器,但是管理员发现部分主机并没有正确获取到该DHCP服务…

开发一套pacs系统需要考虑哪些因素?

PACS全称Picture Archivingand Communication Systems。它是应用在医院影像科室的系统&#xff0c;主要的任务就是把日常产生的各种医学影像&#xff08;包括核磁&#xff0c;CT&#xff0c;超声&#xff0c;X光机&#xff0c;红外仪、显微仪等设备产生的图像&#xff09;通过各…

每日汇评:黄金上破2161美元纪录高位,有可能进一步上涨?

周四早间&#xff0c;金价在2150美元的历史高点附近盘整&#xff0c;并成功上破2160历史高位&#xff1b; 美元在美债收益率的压力下逐步走低&#xff0c;市场期待更多鲍威尔讲话和美国就业数据&#xff1b; 日线图上的RSI指标超买状况继续令黄金买家保持谨慎&#xff1b; 金价…

电脑蓝牙在哪里打开?不同系统详解

在现代计算机的多功能性中&#xff0c;蓝牙技术的广泛应用使得我们能够轻松连接各种外部设备&#xff0c;实现无线传输和分享。无论是连接无线耳机、键盘&#xff0c;还是与其他设备快速交换文件&#xff0c;蓝牙在电脑中的角色很重要。然而&#xff0c;对于一些用户而言&#…

ChatGPT提问技巧——标准提示

ChatGPT提问技巧——标准提示 标准提示是一种通过向模型提供一个具体要完成的任务&#xff0c;指导ChatGPT输出的简单方式。例如&#xff0c;如果你想生成一个新闻的总结&#xff0c;你要提供一个任务像这样的“总结一下这篇新闻文章“。 提示格式&#xff1a;”生成【任务】…

数组的内存执行原理

一.Java内存分配介绍 JVM虚拟机会在内存中执行程序 java内存分配介绍 方法区&#xff0c;栈&#xff0c;堆 首先将编译过后的.class文件送入方法区中。当类开始运行时将方法调入栈内存中&#xff0c;变量也是属于方法的&#xff0c;因此同方法一起进入栈内存中。当main方法要…

Java面试篇【MyCat】常见面试题(2024最新)

Mycat 1.Mycat 分库分表中间件&#xff0c;将存放在一个数据库的数据存放在不同的多个数据库中。来分散负载。 scheme 逻辑库&#xff0c;对应mysql的数据库&#xff0c;一个逻辑库定义了包含的所有table.是数据库集群对外的统一访问接口。table 逻辑表&#xff0c;和物理数…

C++的类与对象(三)

目录 类的6个默认成员函数 构造函数 语法 特性 析构函数 特性 对象的销毁顺序​​​​​​​ 类的6个默认成员函数 问题&#xff1a;一个什么成员都没的类叫做空类&#xff0c;空类中真的什么都没有吗&#xff1f; 基本概念&#xff1a;任何类在什么都不写时&#xff…

【Linux C | 网络编程】多播的概念、多播地址、UDP实现广播的C语言例子

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…

[OpenWrt 22.03] ttylogin添加登录密码与禁止登录的配置

ttylogin 的使用 Openwrt 串口默认是没有密码的。Openwrt启动后,一个默认的密码将被启用去保护ssh登录和页面(http)登录,而串口登录密码却是空缺的。 对于 Openwrt,当内核初始化后,就会启动第一个进程 init,init进程会进行一系列的系统初始化工作,然后会读取 /etc/in…

vue 使用element plus 菜单时,折叠文字不消失

问题&#xff1a; 菜单折叠时&#xff0c;title文本无法消失&#xff0c;同时下拉箭头还会存在 解决方法&#xff1a; 查看项目中是否有div标签 原因 div和p标签都是块级元素&#xff0c;可能是这个原因 所以把项目中的p标签改为span标签 div改为template即可解决

java当中的栈和队列

一、Java中的栈 1.常用方法 注意上面的peek()方法和pop()方法的区别&#xff01; 2.实例 import java.util.Stack; public class StackTest { public static void main(String[] args) { Stack<String> stack new Stack<String>(); System.out.println(&qu…

Clion调试QT程序qDebug()、cout控制台无输出的可能解决方法

qDebug()不输出 在当前项目配置中添加一个环境变量 方法一、单独为配置 QT_ASSUME_STDERR_HAS_CONSOLE1 方法二、全局配置&#xff08;系统变量&#xff09; 一劳永逸 效果 cout不输出 Clion在debug调试C/C的时候&#xff0c;printf/cout不会实时输出情况 结果同上~ 谢阅…

NoSQL--2.MongoDB配置(Windows版)

目录 2.MongdoDB配置 2.1 Windows环境下操作 2.1.1 注册MongDB Atlas&#xff1a; 2.1.2 MongoDB Community Server Download&#xff1a; 2.1.3 启动MondgoDB服务&#xff1a; 2.1.3.1 命令行参数的方式启动MongoDB服务&#xff1a; 2.1.3.2 使用配置文件方式启动Mongo…

电脑怎么改照片大小kb?让你的照片尺寸更合适!

随着数字摄影的普及&#xff0c;我们经常需要处理各种大小的照片。有时候&#xff0c;为了上传至特定的网站或平台&#xff0c;或者为了节省存储空间&#xff0c;我们需要调整照片的大小&#xff0c;特别是其KB&#xff08;千字节&#xff09;值。那么&#xff0c;电脑怎么改照…

react tab选项卡吸顶实现

react tab选项卡吸顶实现&#xff0c;直接上代码&#xff08;代码有注释&#xff09; tsx代码 /* eslint-disable react-hooks/exhaustive-deps */ import React, { useEffect, useState } from "react"; import DocumentTitle from react-document-title import s…

【HTML】HTML基础7.2(有序列表)

目录 标签 效果 注意 标签 <ol> <li>列表内容</li> <li>列表内容</li> <li>列表内容</li> <li>列表内容</li> 。。。。。。 </ol> 效果 代码 <ol><li>银河护卫队 10000000000</li><l…

关于 selinux 规则

1. 查看selinux状态 SELinux的状态&#xff1a; enforcing&#xff1a;强制&#xff0c;每个受限的进程都必然受限 permissive&#xff1a;允许&#xff0c;每个受限的进程违规操作不会被禁止&#xff0c;但会被记录于审计日志 disabled&#xff1a;禁用 相关命令&#xf…

算法DFS 复习

思路&#xff1a;for 代表的是每一位的纵向&#xff0c;数字变化&#xff0c;dfs 代表的是横向的&#xff0c;位置变化。vis 来做到每个枚举的数不重复&#xff0c;并且要在搜索前记录&#xff0c;搜索后还原。模拟该样例 dfs3 的时候是输出&#xff0c;dfs0&#xff0c;1&…