深度解析 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/186316.shtml

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

相关文章

设计模式之装饰模式(2)--有意思的想法

目录 背景概述概念角色 基本代码分析❀❀花样重难点聚合关系认贼作父和认孙做父客户端的优化及好处继承到设计模式的演变过程 总结 背景 这是我第二次写装饰模式&#xff0c;这一次是在上一次的基础上进一步探究装饰模式&#xff0c;这一次有了很多新的感受和想法&#xff0c;也…

BUUCTF john-in-the-middle 1

BUUCTF:https://buuoj.cn/challenges 题目描述&#xff1a; 注意&#xff1a;得到的 flag 请包上 flag{} 提交 密文&#xff1a; 下载附件&#xff0c;解压得到john-in-the-middle.pcap文件。 解题思路&#xff1a; 1、双击文件&#xff0c;打开wireshark。 看到很多http流…

基于springboot实现的在线考试系统

一、系统架构 前端&#xff1a;html | js | css | jquery | bootstrap 后端&#xff1a;springboot | springdata-jpa 环境&#xff1a;jdk1.7 | mysql | maven 二、 代码及数据库 三、功能介绍 01. 登录页 02. 管理员端-课程管理 03. 管理员端-班级管理 04. 管理员端-老师管理…

AT89S52单片机智能寻迹小车自动红外避障趋光检测发声发光设计

wx供重浩&#xff1a;创享日记 对话框发送&#xff1a;寻迹 获取完整说明报告源程序数据 小车具有以下几个功能&#xff1a;自动避障功能&#xff1b;寻迹功能&#xff08;按路面的黑色轨道行驶&#xff09;&#xff1b;趋光功能&#xff08;寻找前方的点光源并行驶到位&…

C++ ini配置文件的简单读取使用

ini文件就是简单的section 下面有对应的键值对 std::map<std::string, std::map<std::string, std::string>>MyIni::readIniFile() {std::ifstream file(filename);if (!file.is_open()) {std::cerr << "Error: Unable to open file " << …

以STM32CubeMX创建DSP库工程方法一

以STM32CubeMX创建DSP库工程方法 略过时钟树的分配和UART的创建等&#xff0c;直接进入主题生成工程文件 它们中的文件功能如下&#xff1a; 1&#xff09;BasicMathFunctions 基本数学函数&#xff1a;提供浮点数的各种基本运算函数&#xff0c;如向量加减乘除等运算。 2&…

【MATLAB】EWT分解+FFT+HHT组合算法

有意向获取代码&#xff0c;请转文末观看代码获取方式~也可转原文链接获取~ 1 基本定义 EWTFFTHHT组合算法是一种广泛应用于信号处理领域的算法&#xff0c;它结合了经验小波变换&#xff08;Empirical Wavelet Transform&#xff0c;EWT&#xff09;、快速傅里叶变换&#x…

SpringBoot查询指定范围内的坐标点

使用Redis geo实现 redis geo是基于Sorted Set来实现的 Redis 3.2 版本新增了geo相关命令&#xff0c;用于存储和操作地理位置信息。提供的命令包括添加、计算位置之间距离、根据中心点坐标和距离范围来查询地理位置集合等&#xff0c;说明如下: geoadd&#xff1a;添加地理…

DCDC前馈电容与RC串并联电路

一、RC串并联电路特性分析 1、RC串联电路 RC 串联的转折频率&#xff1a; f01/&#xff08;2πR1C1&#xff09;&#xff0c;当输入信号频率大于 f0 时&#xff0c;整个 RC 串联电路总的阻抗基本不变了&#xff0c;其大小等于 R1。 2、RC并联电路 RC 并联电路的转折频率&…

02、Tensorflow实现手写数字识别(数字0-9)

02、Tensorflow实现手写数字识别&#xff08;数字0-9&#xff09; 01、Tensorflow实现二元手写数字识别&#xff08;二分类问题&#xff09; 02、Tensorflow实现手写数字识别&#xff08;数字0-9&#xff09; 开始学习机器学习啦&#xff0c;已经把吴恩达的课全部刷完了&…

zookeeper集群和kafka集群

&#xff08;一&#xff09;kafka 1、kafka3.0之前依赖于zookeeper 2、kafka3.0之后不依赖zookeeper&#xff0c;元数据由kafka节点自己管理 &#xff08;二&#xff09;zookeeper 1、zookeeper是一个开源的、分布式的架构&#xff0c;提供协调服务&#xff08;Apache项目&…

【Openstack Train安装】二、NTP安装

网络时间协议&#xff1a;Network Time Protocol&#xff08;NTP&#xff09;是用来使计算机时间同步化的一种协议&#xff0c;它可以使计算机对其服务器或时钟源&#xff08;如石英钟&#xff0c;GPS等等)做同步化&#xff0c;它可以提供高精准度的时间校正&#xff08;LAN上与…

ACM程序设计课内实验(2) 排序问题

基础知识‘ sort函数 C中的sort函数是库中的一个函数&#xff0c;用于对容器中的元素进行排序。它的原型如下&#xff1a; template <class RandomAccessIterator, class Compare> void sort (RandomAccessIterator first, RandomAccessIterator last, Compare comp);参数…

IC设计简单概述

IC设计行业是一个高科技行业&#xff0c;有着复杂而细致的分工&#xff0c;严格的流程规范、多种不同类型的EDA工具。下面简单概述以下几个方面。 IC设计公司的分类 IC设计公司有多种分类方法。若按有无芯片生产能力来分&#xff0c;可以分为兼具设计与生产能力&#xff08;I…

Shopee引流妙招!Shopee产品标签重要吗?教你有效打标签引爆流量!

对Shopee平台的卖家来说&#xff0c;在新产品上架时除了要注重产品title、介绍以及图文的优化&#xff0c;还有一件事情很重要&#xff0c;那就是——产品打标签。 对于每个跨境电商卖家来讲&#xff0c;对产品打标签都是必不可少的一个运营环节 下面小宇就来告诉大家&#xf…

SpringSecurity的默认登录页的使用

SpringSecurity的默认登录页的使用 01 前期准备 引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--mysql驱动--><dependency><grou…

主机的容器化技术介绍

☞ ░ 前往老猿Python博客 ░ https://blog.csdn.net/LaoYuanPython 一、什么是容器 容器是一个标准化的单元&#xff0c;是一种轻量级、可移植的软件打包技术&#xff0c;容器将软件代码及其相关依赖打包&#xff0c;使应用程序可以在任何计算介质运行。例如开发人员在自己的…

python pytorch实现RNN,LSTM,GRU,文本情感分类

python pytorch实现RNN,LSTM&#xff0c;GRU&#xff0c;文本情感分类 数据集格式&#xff1a; 有需要的可以联系我 实现步骤就是&#xff1a; 1.先对句子进行分词并构建词表 2.生成word2id 3.构建模型 4.训练模型 5.测试模型 代码如下&#xff1a; import pandas as pd im…

命名管道:简单案例实现

&#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;Linux &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&#x1f349;留言 本博客主要内容讲解了什么是命名管道&#xff0c;匿名管道和命名管道的…

深入了解Rabbit加密技术:原理、实现与应用

一、引言 在信息时代&#xff0c;数据安全愈发受到重视&#xff0c;加密技术作为保障信息安全的核心手段&#xff0c;得到了广泛的研究与应用。Rabbit加密技术作为一种新型加密方法&#xff0c;具有较高的安全性和便捷性。本文将对Rabbit加密技术进行深入探讨&#xff0c;分析…