Spring Boot 处理过滤器(filter )中抛出的异常

前言:

在改造老项目登录功能的时候,使用了过滤器对 token 进行有效性验证,验证通过继续进行业务请求,验证不通过则抛出校验异常。

过程:

技术方案拟定后,就着手开始改造,一切都很顺畅,可是在异常场景模拟的时候,怎么也得不到想要的异常 code,我在过滤器的校验中明明是抛出了异常,为什么没有得到想要的结果呢?

过滤器代码如下:

@Slf4j
//@WebFilter(filterName = "myFilter", urlPatterns = "/*")
public class MyAuthenticationFilter implements Filter {//不拦截的 URLprivate final static String EXCLUDES_URI = "/api/workflow/*";@Overridepublic void init(FilterConfig filterConfig) throws ServletException {Filter.super.init(filterConfig);}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {//为了方便获取 header 信息 对 HttpServletRequest 进行强转HttpServletRequest request = (HttpServletRequest) servletRequest;String requestUri = request.getRequestURI();//需要忽略的 url 地址Pattern pattern = Pattern.compile(EXCLUDES_URI);//是否放行boolean isExclude = pattern.matcher(requestUri).find();//用户信息UserInfoVO userInfoVO = null;if (!isExclude) {//校验认证信息userInfoVO = validateAuthorization(request);if (ObjectUtil.isNull(userInfoVO)) {//校验认证信息 失败 可能解析 token 异常 可能没有解析到正确的工号throw new AuthorizationValidationException(ResultCode.CAS_AUTHORIZATION);}}//设置用户信息UserContextHolder.setUser(userInfoVO);filterChain.doFilter(servletRequest, servletResponse);}@Overridepublic void destroy() {//将ThreadLocal数据清空UserContextHolder.remove();Filter.super.destroy();}/*** @Description: 校验 Authorization* @Date: 2024/4/3 10:25*/public UserInfoVO validateAuthorization(HttpServletRequest request) {//获取 AuthorizationString authorization = request.getHeader(CommConstant.AUTHORIZATION);if (StringUtils.isBlank(authorization)) {StringBuffer requestUrl = request.getRequestURL();log.info("Authorization 为空的请求url:{}", requestUrl);//Authorization 为空 没有登录return null;}//过滤器是 servlet 规范中定义的 不归 spring 容器管理  无法直接注入 spring 中的 bean 直接注入会为 null//只能够自己去 容器中获取 beanApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());assert context != null;AuthServiceImpl iAuthService = (AuthServiceImpl) context.getBean("authServiceImpl");//检验 authorizationreturn iAuthService.validateToken(authorization);}
}

配置过滤器代码:

package com.zt.zteam.main.configurer;import com.zt.zteam.main.filter.MyAuthenticationFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class MyAuthenticationFilterConfig {@Bean("myFilterAuthentication")public FilterRegistrationBean<MyAuthenticationFilter> filterAuthenticationRegistration() {//设置过滤器FilterRegistrationBean<MyAuthenticationFilter> registration = new FilterRegistrationBean<>();registration.setFilter(new MyAuthenticationFilter());//设置过滤器优先级 数字越小优先级越高registration.setOrder(-1);return registration;}}

配置过滤方式二:

过滤器类上加 @WebFilter(filterName = “myFilter”, urlPatterns = “/*”) 注解,同时在启动类上加 @ServletComponentScan({“com.xxx.xxx.xxx.filter”}) 注解。

全局异常处理器代码如下:

@RestControllerAdvice(basePackages = "com.my.study")
//@Order(Ordered.HIGHEST_PRECEDENCE)
@Slf4j
public class GlobalExceptionHandler {/*** 交易异常*/@ExceptionHandler(ValidateException.class)public Result<?> validateExceptionHandler(HttpServletRequest request, ValidateException e) {log.error("校验异常,方法:{}", request.getRequestURI(), e);return ResultGenerator.genResult(e.getCode(), e.getMessage());}/*** 处理业务异常*/@ExceptionHandler(ServiceException.class)public Result<?> bizExceptionHandler(HttpServletRequest request, ServiceException e) {log.error("业务异常,方法:{}", request.getRequestURI(), e);return ResultGenerator.genResult(e.getCode(), e.getMessage());}/*** 处理空指针异常*/@ExceptionHandler(NullPointerException.class)public Result<?> npeHandler(HttpServletRequest request, NullPointerException e) {log.error("空指针异常,方法:{}", request.getRequestURI(), e);return ResultGenerator.genFailResult(e.getMessage());}/*** 处理其他异常*/@ExceptionHandler(Exception.class)public Result<?> exceptionHandler(HttpServletRequest request, Exception e) {log.error("其它异常,方法:{}", request.getRequestURI(), e);Result<?> result = new Result<>();result.setCode(ResultCode.INTERNAL_SERVER_ERROR).setMessage(e.getMessage() + "--接口 [" + request.getRequestURI() + "] 内部错误,请联系管理员");return result;}@ExceptionHandler({BusinessException.class})public Result<?> businessException(HttpServletRequest request, Exception e) {log.error("业务处理异常信息,方法:{},异常信息:" ,request.getRequestURI(), e);Result<?> result = new Result<>();result.setCode(ResultCode.INTERNAL_SERVER_ERROR).setMessage(e.getMessage());return result;}/*** 这个是valid注解校验参数时,校验不通过的异常*/@ExceptionHandler(MethodArgumentNotValidException.class)public Result<?> validateMethodExceptionHandler(HttpServletRequest request, MethodArgumentNotValidException e) {log.error("请求参数异常,方法:{}", request.getRequestURI(), e);Result<?> result = new Result<>();result.setCode(ResultCode.INTERNAL_SERVER_ERROR).setMessage(e.getBindingResult().getFieldErrors().get(0).getDefaultMessage());return result;}/*** token 认证验证*/@ExceptionHandler(AuthorizationValidationException.class)public Result<?> authorizationValidationExceptionHandler(HttpServletRequest request, Exception e) {log.error("CAS 认证异常,方法:{}", request.getRequestURI(), e);Result<?> result = new Result<>();result.setCode(ResultCode.UNAUTHORIZED).setMessage(e.getMessage() + "--接口 [" + request.getRequestURI() + "] 内部错误,请联系管理员");return result;}}

测试结果:

{"timestamp": "2024-04-16T02:04:48.091+00:00","status": 500,"error": "Internal Server Error","message": "","path": "/api/logout"
}

没有得到我们预期的 code:401。

问题分析:

看起来代码显示的抛出了异常,也设置了全局异常处理器,但是并没有返回想要的异常状态码,至此感觉走到了死胡同,此时想到了老办法,debug 调试,经过多次 debug 调试,发现全局异常处理器没有拦截到任何异常,这就很能说明问题了,也就是全局异常处理器,根本捕获不到过滤器 filter 抛出的异常,那怎么办呢?我们知道全局异常过滤器是一定可以捕获到 Controller 的异常的,此时灵机一动,当出现异常后,在过滤器 filter 中使用 try catch 自己处理,然后使用 forward 转发请求到指定 Controller 不就可以了吗,方案有了,着手开始测试。

注意:@ControllerAdvice 注解只处理经过 Controller 的异常,不经过 Controller 的异常 @ControllerAdvice 注解不进行处理。

调整后的过滤器 filter 代码如下:

@Slf4j
public class MyAuthenticationFilter implements Filter {//不拦截的 URLprivate final static String EXCLUDES_URI = "/api/workflow/*";@Overridepublic void init(FilterConfig filterConfig) throws ServletException {Filter.super.init(filterConfig);}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {//为了方便获取 header 信息 对 HttpServletRequest 进行强转HttpServletRequest request = (HttpServletRequest) servletRequest;String requestUri = request.getRequestURI();//需要忽略的 url 地址Pattern pattern = Pattern.compile(EXCLUDES_URI);//是否放行boolean isExclude = pattern.matcher(requestUri).find();//用户信息UserInfoVO userInfoVO = null;try {if (!isExclude) {//校验认证信息userInfoVO = validateAuthorization(request);if (ObjectUtil.isNull(userInfoVO)) {//校验认证信息 失败 可能解析 token 异常 可能没有解析到正确的工号throw new AuthorizationValidationException(ResultCode.CAS_AUTHORIZATION);}}//设置用户信息UserContextHolder.setUser(userInfoVO);filterChain.doFilter(servletRequest, servletResponse);} catch (AuthorizationValidationException e) {request.setAttribute(CommConstant.FILTER_ERROR, e);request.getRequestDispatcher(CommConstant.FILTER_ERROR_PATH).forward(request, servletResponse);} catch (IOException e) {e.printStackTrace();} catch (ServletException e) {e.printStackTrace();}}@Overridepublic void destroy() {//将ThreadLocal数据清空UserContextHolder.remove();Filter.super.destroy();}/*** @Description: 校验 Authorization* @Date: 2024/4/3 10:25*/public UserInfoVO validateAuthorization(HttpServletRequest request) {//获取 AuthorizationString authorization = request.getHeader(CommConstant.AUTHORIZATION);if (StringUtils.isBlank(authorization)) {StringBuffer requestUrl = request.getRequestURL();log.info("Authorization 为空的请求url:{}", requestUrl);//Authorization 为空 没有登录return null;}//过滤器是 servlet 规范中定义的 不归 spring 容器管理  无法直接注入 spring 中的 bean 直接注入会为 null//只能够自己去 容器中获取 beanApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());assert context != null;AuthServiceImpl iAuthService = (AuthServiceImpl) context.getBean("authServiceImpl");//检验 authorizationreturn iAuthService.validateToken(authorization);}
}

请求转发的关键代码:

//filterErrorpublic final static String FILTER_ERROR = "filterError";//filterError pathpublic final static String FILTER_ERROR_PATH = "/throw-error";//设置异常信息request.setAttribute(CommConstant.FILTER_ERROR, e);//转发request.getRequestDispatcher(CommConstant.FILTER_ERROR_PATH).forward(request, servletResponse);

异常处理 Controller 代码:

@Slf4j
@RestController
public class FilterExceptionController {@ApiOperation(value = "过滤器异常处理", produces = "application/json")@RequestMapping(CommConstant.FILTER_ERROR_PATH)public Result<String> testRedis(HttpServletRequest request) {Object attribute = request.getAttribute(CommConstant.FILTER_ERROR);if(attribute instanceof AuthorizationValidationException){throw new AuthorizationValidationException(ResultCode.CAS_AUTHORIZATION);}throw new BusinessException("业务异常");}
}

测试结果:

在这里插入图片描述
总结:

通过请求转发的方式,我们解决了过滤器 filter 异常无法捕获的问题,在转发的过程中我们尽量使用 request.getRequestDispatcher(“/path”).forward(request, response) 这种方式,此方式只会在服务端内部转发,客户端地址不会发生任何改变,如果使用response.sendRedirect(“/path”) 进行请求转发,客户端地址会发生改变。

在 Spring 应用中我们不建议优先使用过滤器 filter,建议优先使用拦截器 Interceptor,本文只是分享过滤器 filter 中的异常处理方式,希望帮助到有需要的伙伴们。

过滤器和拦截器的区别传送门:
Spring 拦截器实现请求拦截与参数处理【拦截器(Interceptor)和过滤器(Filter)的区别】

如有错误的地方欢迎指出纠正。

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

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

相关文章

Oracle子查询

这里写目录标题 子查询子查询的分类1.非相关子查询单行单列单行多列单列多行多行多列 2.相关子查询 子查询 定义&#xff1a;一个查询语句中嵌套一个或多个查询语句&#xff0c;里层的查询语句的结果成为了外层查询语句的一部分。 外层的查询叫主查询 里层的查询叫子查询 子查…

Linux学习:进程(5)进程控制

目录 1. 进程创建1.1 进程创建的方式与过程1.2 写时拷贝1.3 补充知识 2. 进程终止2.1 main函数返回值与进程退出码2.2 进程退出码的意义2.3 进程的执行结果与异常信号2.4 进程终止方式&#xff1a;exit与_exit 3. 进程等待3.1 进程创建与回收3.2 进程等待与回收的方式3.3 wait与…

鸿蒙语言TypeScript学习第18天:【泛型】

1、TypeScript 泛型 泛型&#xff08;Generics&#xff09;是一种编程语言特性&#xff0c;允许在定义函数、类、接口等时使用占位符来表示类型&#xff0c;而不是具体的类型。 泛型是一种在编写可重用、灵活且类型安全的代码时非常有用的功能。 使用泛型的主要目的是为了处…

树莓派安装tensorflow

树莓派安装tensorflow 使用编译好的版本自己选择版本进行编译armv71 架构 教程转载 使用编译好的版本 下载tensorflow编译好的版本 https://github.com/lhelontra/tensorflow-on-arm/tags由于python版本支持有限可能需要自己安装python 安装对应的python 自己选择版本进行编译…

【动态规划】【01背包 给定背包容量,装满背包最多有多少个物品】Leetcode 474. 一和零

【动态规划】【01背包 给定背包容量&#xff0c;装满背包最多有多少个物品】Leetcode 474. 一和零 解法 ---------------&#x1f388;&#x1f388;474. 一和零 题目链接&#x1f388;&#x1f388;------------------- 纯 0 - 1 背包 是求 给定背包容量 装满背包 的最大价值…

【随笔】Git 高级篇 -- 远程与本地不一致导致提交冲突 git push --rebase(三十一)

&#x1f48c; 所属专栏&#xff1a;【Git】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#x1f496; 欢迎大…

如何爬出 Kotlin 协程死锁的坑?

作者&#xff1a;悬衡 一、前言 在 Java 中有一个非常经典的死锁问题, 就是明明自己已经占用了线程池, 却还继续去申请它, 自己等自己, 就死锁了, 如下图和代码: // 这段代码将死锁到天荒地老final ExecutorService executorService Executors.newSingleThreadExecutor();exe…

【C 数据结构】单链表

文章目录 【 1. 基本原理 】1.1 链表的节点1.2 头指针、头节点、首元节点 【 2. 链表的创建 】2.0 创建1个空链表&#xff08;仅有头节点&#xff09;2.1 创建单链表&#xff08;头插入法&#xff09;*2.2 创建单链表&#xff08;尾插入法&#xff09; 【 3. 链表插入元素 】【…

爱之旅 | 爱的守望与宽恕

爱其实是一种持续的稳定情绪输出与反馈的表现。 情绪稳定的前提是丰富。 比如生活的丰富&#xff0c;有物质财富、社会关系、健康、知识和经验等。 一个人的生活如果多元化&#xff0c;拥有丰富的兴趣爱好、健康的社交关系、持续的个人成长&#xff0c;在面对生活中的挑战时自…

【SpringBoot实战篇】登录认证

&#x1f340;&#x1f338;明确需求--接口文档--思路分析--开发--测试&#x1f338;&#x1f340;&#x1f495; 1 明确需求 2 接口文档 登录 3 思路分析 UserServic、UserMapper在注册的时候已经实现 现在我们重点看UserController 控制器 4 开发&#xff08;实现&#xff0…

做熟思维100历年真题700道,高效备考2024年思维100春季线上赛

今天是2024年4月17日&#xff0c;距离2024年春季思维100活动第一阶段的线上比赛4月20日还有3天。尽管主办方未提供2024年思维100活动的考试重点和大纲&#xff0c;但是我们仍然可以从历年的思维100真题中来分析和推测&#xff0c;把历年真题和背后的知识点吃透了&#xff0c;举…

Flink CDC:使用 Flink SQL 将多表写入一个 Kafka Topic 以及 Flink 作业数量的测试

博主历时三年精心创作的《大数据平台架构与原型实现&#xff1a;数据中台建设实战》一书现已由知名IT图书品牌电子工业出版社博文视点出版发行&#xff0c;点击《重磅推荐&#xff1a;建大数据平台太难了&#xff01;给我发个工程原型吧&#xff01;》了解图书详情&#xff0c;…

JQuery(四)---【使用JQuery实现动画效果】

目录 前言 一.隐藏和显示 1.1使用方法 1.2案例演示(1) 1.3隐藏/显示效果一键切换 二.淡入淡出效果 2.1使用方法 2.2案例演示(fadeIn) 2.3案例演示(fadeOut) 2.4案例演示(fadeToggle) 2.5案例演示(fadeTo) 三.滑动 3.1使用方法 3.2案例演示(slideDown) 3.3案例演示…

音频---数字mic

一、常见的数字mic pdm麦通过codec芯片将数字麦转换为i2s信号输入到SOC 纯pdm麦就是直接进入SOC的pdm接口&#xff0c;走的是PDM信号&#xff0c;PDM信号就是两个线&#xff0c;一根数据线一根时钟线&#xff08;如顺芯ES7201/7202把MIC信号转换成PDM&#xff09;。 二、DMIC…

pixhawk控制板的ArduPilot固件编译

0. 环境 - ubuntu18&#xff08;依赖python2和pip&#xff0c;建议直接ubuntu18不用最新的&#xff09; - pixhawk 2.4.8 - pixhawk 4 1. 获取源码 # 安装git sudo apt install git # 获取源码 cd ~/work git clone --recurse-submodules https://github.com/ArduPilot/a…

MDC使用手册精讲

MDC 背景&#xff1a; 线上排查问题时&#xff0c;请求在多个微服务之间进行调用&#xff0c;并发量较大的情况下&#xff0c;想跟踪某一个请求的链路&#xff0c;是需要花费一些时间才能梳理出来&#xff0c;而且还依赖于你的业务字段。而我们需要的是快速定位&#xff0c;快…

飞桨Ai(二)paddle使用CPU版本可以正常识别,切换为GPU版本时无法识别结果

一、问题描述&#xff1a; 刚开始用paddle的CPU版本&#xff0c;对训练好的模型进行推理&#xff0c;正常识别出想要的结果后来尝试使用paddle的GPU版本&#xff0c;然后发现识别出来是空的 二、系统思路&#xff1a; 最终系统环境如下&#xff1a; 系统&#xff1a;win10 …

window轻松使用k8s

Docker Desktop安装篇 1、win安装 1、下载安装包 https://www.docker.com/products/docker-desktop/ 官网下载安装包 2、配置win支持虚拟化 不勾选Hyper-V&#xff0c;它和Windows Subsystem for Linux (WSL) 是两套功能&#xff0c;这里不选他 3、安装WSL配置window支持lin…

采用4G、5G实现无线视频监控,流量过大费用高,如何降低网络流量?

目录 一、高清视频监控中使用的4G和5G介绍 &#xff08;一&#xff09;4G物联网卡&#xff1a; 1、数据传输与稳定性 2、应用与优势 &#xff08;二&#xff09;5G物联网卡&#xff1a; 1、数据传输与速率 2、应用场景 二、4G/5G流量池 三、视频监控的流量使用 …

Java之JVM、JUC面试题笔记(持续更新)

CountDownLatch和CyclicBarrier JUC 并发编程_juc并发编程-CSDN博客 java 类加载机制&#xff1f;如何实现自定义类加载器&#xff1f;findClass 与 loadClass 的区别&#xff1f; 在Java中&#xff0c;自定义类加载器通常是通过继承java.lang.ClassLoader类并重写其findClas…