Spring Security 自定义异常失效?从源码分析到解决方案

🚀 作者主页: 有来技术
🔥 开源项目: youlai-mall 🍃 vue3-element-admin 🍃 youlai-boot
🌺 仓库主页: Gitee 💫 Github 💫 GitCode
💖 欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请纠正!

目录

  • 问题描述
  • 项目关键代码
    • 自定义异常处理器
    • Spring Security 配置
    • 全局异常处理器
    • 访问权限测试接口
  • 问题分析
  • 解决方案
  • 源码阅读
    • ExceptionTranslationFilter#doFilter
    • DispatcherServlet#doDispatch
    • DispatcherServlet#processHandlerException
    • ExceptionHandlerExceptionResolver
  • 结语
  • 参考

问题描述

在基于 Spring Boot 3 + Spring Security 6 的权限管理系统开源项目 youlai-boot 中,根据官方提供的思路重写 AccessDeniedHandler 实现自定义异常处理,但发现该实现并没有生效,而是被全局异常捕获。期望的响应是 {"code":"A0301","msg":"访问未授权"},但实际上获得的响应与期望的不符,具体响应如下图所示:

项目关键代码

下面贴出关键代码,完整代码:youlai-boot

自定义异常处理器

package com.youlai.system.core.security.exception;/*** Spring Security 访问异常处理器** @author haoxr* @since 2022/10/18*/
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {ResponseUtils.writeErrMsg(response, ResultCode.ACCESS_UNAUTHORIZED);}
}

Spring Security 配置

package com.youlai.system.core.security.config;/*** Spring Security 权限配置** @author haoxr* @since 2023/2/17*/
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {private final MyAuthenticationEntryPoint authenticationEntryPoint;private final MyAccessDeniedHandler accessDeniedHandler;@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http// ....exceptionHandling(httpSecurityExceptionHandlingConfigurer ->httpSecurityExceptionHandlingConfigurer.authenticationEntryPoint(authenticationEntryPoint) // 未认证异常处理.accessDeniedHandler(accessDeniedHandler) // 未授权访问异常处理)// ...;return http.build();}}

全局异常处理器

package com.youlai.system.common.exception;/*** 全局系统异常处理器**/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {// ...// 捕获 Exception@ExceptionHandler(Exception.class)@ResponseStatus(HttpStatus.BAD_REQUEST)public <T> Result<T> handleException(Exception e) {log.error("unknown exception: {}", e.getMessage());return Result.failed(e.getLocalizedMessage());}}

访问权限测试接口

在删除角色接口中,使用了 Spring Security 提供的权限控制注解 @PreAuthorize 来进行权限校验。使用该注解可以在方法级别上对用户的权限进行校验,只有具有相应权限的用户才能执行该方法,否则会提示用户无权访问。

@Operation(summary = "删除角色")
@DeleteMapping("/{ids}")
@PreAuthorize("@ss.hasPerm('sys:role:delete')")
public Result deleteRoles(@Parameter(description ="删除角色,多个以英文逗号(,)分割") @PathVariable String ids
) {boolean result = roleService.deleteRoles(ids);return Result.judge(result);
}

问题分析

现象:当存在全局异常处理器时,自定义的 Spring Security 权限异常拦截处理器失效;而当移除全局异常处理器时,自定义的权限异常拦截处理器将正常生效。

猜测:全局异常处理器会干扰到 Spring Security 的权限异常处理流程,导致自定义的处理器无法按预期执行。

根据猜测,直接定位 Spring Security 异常处理和转换的过滤器 ExceptionTranslationFilterdoFilter 方法。

通过分析代码逻辑,可以确定问题的根源在于无权限访问异常被全局异常处理器捕获并处理掉了,因此不会进入 catch 分支执行 handleSpringSecurityException 方法。具体执行过程放在下文的源码解析章节。

解决方案

为了确保异常能够传递给 Spring Security 的自定义异常处理器,你可以对全局异常处理器(GlobalExceptionHandler)进行修改。如果异常是 AuthenticationExceptionAccessDeniedException,则继续将其抛出以便交给自定义处理器处理。

package com.youlai.system.common.exception;//.../*** 全局系统异常处理器**/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {@ExceptionHandler(Exception.class)@ResponseStatus(HttpStatus.BAD_REQUEST)public <T> Result<T> handleException(Exception e) throws Exception{// 将 Spring Security 异常继续抛出,以便交给自定义处理器处理if (e instanceof AccessDeniedException|| e instanceof AuthenticationException) {throw e; }log.error("unknown exception: {}", e.getMessage());return Result.failed(e.getLocalizedMessage());}
}

通过这样的修改,当遇到 AuthenticationException 或者 AccessDeniedException 异常时,它们将被继续抛出,从而能够进入到 Spring Security 的自定义异常处理器进行处理。其他类型的异常仍然会在当前异常处理逻辑中进行处理。

修改后,按照自定义异常处理的输出,测试正常。

源码阅读

接下来通过源码阅读分析:为什么在存在全局异常处理器的情况下,Spring Security的无权限访问异常无法被自定义异常处理器处理?

ExceptionTranslationFilter#doFilter

在没有全局异常处理器的情况下,异常没有被拦截处理,会进入 catch 分支,由自定义异常处理器处理。

但如果存在全局异常处理器,则不会抛出异常,也就不会进入 catch 分支。

因此,需要进一步探究 chain.doFilter(request, response) 的后续处理,以了解为什么在有全局异常处理器的情况下没有异常抛出。

DispatcherServlet#doDispatch

DispatcherServlet#processHandlerException

DispatcherServlet#processDispatchResult 调用 DispatcherServlet#processHandlerException 方法处理异常

有无全局异常处理器在这个方法可以体现出区别了。

  • 有全局异常处理器

  • 无全局异常处理器

    image-20231130165031428

全局异常处理器 得到 exMv 不为 null ,后续不抛出异常,所以需要在 exMv = resolver.resolveException(request, response, handler, ex); 断点看下里面的逻辑。

ExceptionHandlerExceptionResolver

调用栈 HandlerExceptionResolverComposite#resolveException → AbstractHandlerExceptionResolver#resolveException → ExceptionHandlerExceptionResolver#doResolveHandlerMethodException

  • 有全局异常处理器

  • 无全局异常处理器

结语

在对异常处理流程进行详细的源码解读后,我们不仅解决了Spring Security权限异常处理在存在全局异常处理器时失效的问题,还对Spring MVC的异常处理机制有了更深入的了解。这一探索不仅有助于更好地理解框架的内部机制,也为未来的项目调优提供了有益的经验。

参考

  • https://stackoverflow.com/questions/31074040/custom-accessdeniedhandler-not-called
  • https://github.com/spring-projects/spring-security/issues/6908

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

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

相关文章

使用阿里巴巴同步工具DataX实现Mysql与ElasticSearch(ES)数据同步

一、Linux环境要求 二、准备工作 2.1 Linux安装jdk 2.2 linux安装python 2.3 下载DataX&#xff1a; 三、DataX压缩包导入&#xff0c;解压缩 四、编写同步Job 五、执行Job 六、定时更新 6.1 创建定时任务 6.2 提交定时任务 6.3 查看定时任务 七、增量更新思路 一、Linux环境要…

微信小程序js数组对象根据某个字段排序

一、排序栗子 注: 属性字段需要进行转换,如String类型或者Number类型 //升序排序 首元素(element1)在前 降序则(element1)元素在后 data data.sort((element1, element2) >element1.属性 - element2.属性 ); 二、代码 Page({/*** 页面的初始数据*/data: {user:…

SpringSecurity安全授权

目录 前言 正文 1.基本流程 2.基本用法 3.配置项 4.HttpSecurity 方式和内存认证方式 5.认证流程 6.基于数据库查询的登录验证 7.多种角色权限认证 8.自定义权限认证 总结 前言 安全对于任何系统来说都是非常重要的&#xff0c;权限的分配和管理一直都是开发者需…

C语言——输出菱形

法一&#xff1a; #include<stdio.h> #define N 7 //假设输出7层菱形 int main(){int i;//i控制第几行 int j;//j控制每一行空格的循环个数 int k;//k控制每一行*的循环次数 for(i1;i<4;i){//将图形分为两部分,前四行(第一部分) for(j1;j<4-i;j){//输出第i行的…

echarts双折线图

引用 //反应时长 durationCharts categoryCommonChart(studyBehavior.durationCharts, durationCharts) function categoryCommonChart(odata, dom){var myChart echarts.init(document.getElementById(dom));let oarr []oarr odata.series.map(function(item){let color…

随笔-这都是命吗

我与鹏哥、小付有个小群&#xff0c;前几天&#xff0c;鹏哥在群里发了一个图&#xff0c;是他那个城市准备扶持的高新产业&#xff0c;有元宇宙、量子信息、生物制药、人工智能什么的。 先前的时候鹏哥给我说过&#xff0c;当地准备了六百多亩地&#xff0c;准备发展高新产业…

Linux-进程之间的通信

目录 ​编辑 一.什么是进程之间的通信 二.进程之间的通信所访问的数据 三.进程之间的通信是如何做到的 四.基于内存文件级别的通信方式——管道 1.什么是管道 2.管道的建立过程——匿名管道 a.什么是匿名管道 b.匿名管道特点&#xff1a; c.使用匿名管道的…

风格迁移网络修改流程(自用版)

一. AdaAttN-Revisit Attention Mechanism in Arbitrary Neural Style Transfer&#xff08;ICCV2021&#xff09; 下载vgg_normalised.pth打开visdom python -m visdom.server在 train_adaattn.sh 中配置 content_path、style_path 和 image_encoder_path&#xff0c;分别表…

固态硬盘速度测试:硬盘实际性能是否符合标准?

在进行固态硬盘速度测试之前我们先来了解一下固态硬盘的读写速度是什么。固态硬盘的读写速度主要分为顺序读写和随机读写&#xff08;4K&#xff09;。 ​顺序读写&#xff1a;指的是硬盘在读写连贯、集中大文件时候的速度。比如在读取、拷贝单个视频文件时&#xff0c;就是硬盘…

【项目问题解决】IDEA2020.3 使用 lombok 插件 java: 找不到符号 符号: 方法 builder()

目录 lombok找不到符号问题修改 1.问题描述2.问题原因3.解决思路4.解决方案5.总结6.参考 文章所属专区 项目问题解决 1.问题描述 IDEA2020.3 使用 lombok 插件 java: 找不到符号 符号: 方法 builder()&#xff0c;无法使用lombok下应有的注解&#xff0c;一度怀疑是版本问题 …

使用Inno Setup 打包程序文件 怎么把其中一个文件安装时复制到指定系统文件夹

环境: Inno Setup 6.6 Win10 专业版 问题描述: 使用Inno Setup 打包程序文件 怎么把其中一个文件安装时复制到指定系统文件夹 将文件api-ms-win-shcore-scaling-l1-1-1.dll复制到system32里面 解决方案: 1.由于安全和权限的限制,直接在Inno Setup脚本中复制文件到C:\…

C++新经典模板与泛型编程:用成员函数重载实现std::is_class

用成员函数重载实现is_class std::is_class功能&#xff0c;是一个C11标准中用于判断某个类型是否为一个类类型&#xff08;但不是联合类型&#xff09;的类模板。当时在讲解的时候并没有涉及std::is_class的实现代码&#xff0c;在这里实现一下。简单地书写一个IsClass类模板…

python pydoc生成API文档

pydoc是python内置的一个文档生成模块。 pydoc 模块会根据 Python 模块来自动生成文档。 生成的文档可在控制台中显示为文本页面&#xff0c;提供给 Web 浏览器访问或者保存为 HTML 文件。 对于模块、类、函数和方法&#xff0c;显示的文档内容取自文档字符串&#xff08;即 _…

泰凌微(Telink)8258配置串口收发自定义数据

在官网下载SDK后&#xff08;以Mesh SDK为例&#xff09;使用Eclipse打开&#xff0c;对应MCU的配置文件在app_config_8258.h&#xff0c;默认的HCI接口是HCI_USE_NONE&#xff0c;如果改成HCI_USE_UART后可以通过串口收发数据&#xff0c;此时默认接收函数处理的是以Telink的协…

音视频学习(二十)——rtsp收流(udp方式)

前言 本文主要介绍通过udp方式实现rtsp拉流。 流程图 流程说明&#xff1a; 相较于tcp方式“信令数据”复用同一连接拉流&#xff0c;udp方式拉流“信令数据”采用不同的连接&#xff0c;信令传输采用tcp&#xff0c;流数据传输采用udp&#xff1b;客户端向服务端&#xff0…

数据库增删改查(CRUD)进阶版

目录 数据库约束 约束类型 表的设计 1.一对一 2.一对多 3.多对多 增删查改进阶操作 1. 插入查询结果 2.查询 聚合查询 聚合函数 group by having 联合查询 内连接 外连接 自连接 子查询 合并查询 数据库约束 创建表的时候制定的一些规则&#xff0c;在后续…

智能优化算法应用:基于北方苍鹰算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于北方苍鹰算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于北方苍鹰算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.北方苍鹰算法4.实验参数设定5.算法结果6.参考…

生成式人工智能笔记-AIGC笔记

生成式人工智能笔记-AIGC笔记 十多年前&#xff0c;人工智能还只是一个不被人看好的小众领域&#xff0c;但是现在&#xff0c;它却已经成了街头巷尾的热点谈资&#xff0c;几乎任何事情都可以和人工智能联系在一起。 人工智能包括基础层、技术层和应用层。 基础层是人工智能…

收藏!当今最流行的10 种人工智能算法

人工智能的概念始于1956年的达特茅斯会议&#xff0c;由于受到数据、计算力、智能算法等多方面因素的影响&#xff0c;人工智能技术和应用发展经历了多次高潮和低谷。 2022年以来&#xff0c;以ChatGPT为代表的大模型一夜爆火&#xff0c;它能够基于在预训练阶段所见的模式和统…

Python中如何判断List中是否包含某个元素

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 在Python中&#xff0c;判断一个列表&#xff08;List&#xff09;是否包含某个特定元素是常见的任务之一。在本文中&#xff0c;将深入探讨多种判断List成员包含性的方法&#xff0c;并提供丰富的示例代码&…