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

前言:

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

过程:

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

过滤器代码如下:

@Slf4j
//@WebFilter(filterName = "myFilter", urlPatterns = "/*")
public class MyAuthenticationFilter implements Filter {

    //不拦截的 URL
    private final static String EXCLUDES_URI = "/api/workflow/*";

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

   @Override
    public 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);
    }

    @Override
    public void destroy() {
        //将ThreadLocal数据清空
        UserContextHolder.remove();
        Filter.super.destroy();
    }

    /**
     * @Description: 校验 Authorization
     * @Date: 2024/4/3 10:25
     */
    public UserInfoVO validateAuthorization(HttpServletRequest request) {
        //获取 Authorization
        String 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
        //只能够自己去 容器中获取 bean
        ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());
        assert context != null;
        AuthServiceImpl iAuthService = (AuthServiceImpl) context.getBean("authServiceImpl");
        //检验 authorization
        return 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 {

    //不拦截的 URL
    private final static String EXCLUDES_URI = "/api/workflow/*";

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public 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();
        }
    }

    @Override
    public void destroy() {
        //将ThreadLocal数据清空
        UserContextHolder.remove();
        Filter.super.destroy();
    }

    /**
     * @Description: 校验 Authorization
     * @Date: 2024/4/3 10:25
     */
    public UserInfoVO validateAuthorization(HttpServletRequest request) {
        //获取 Authorization
        String 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
        //只能够自己去 容器中获取 bean
        ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());
        assert context != null;
        AuthServiceImpl iAuthService = (AuthServiceImpl) context.getBean("authServiceImpl");
        //检验 authorization
        return iAuthService.validateToken(authorization);
    }
}

请求转发的关键代码:

//filterError
 public final static String FILTER_ERROR = "filterError";

 //filterError path
 public 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)的区别】

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

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

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

相关文章

Linux用户及用户组管理命令

Linux操作系统是一种基于UNIX的多用户、多任务的操作系统。在Linux系统中&#xff0c;用户和用户组的管理是非常重要的&#xff0c;因为它关系到系统安全和多用户环境下的资源共享。本文将详细介绍Linux中用户和用户组管理的相关命令&#xff0c;帮助用户更好地理解和管理Linux…

SpringBoot整合minio服务

这里我选用的是JDK1.8 SpringBoot2.3.12.RELEASE 一、导入依赖 <dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>8.2.2</version> </dependency> 二、导入工具类 注意&#xff1a;需要在…

DP4 最小花费爬楼梯

原题链接&#xff1a;最小花费爬楼梯_牛客题霸_牛客网 目录 1. 题目描述 2. 思路分析 3. 代码实现 1. 题目描述 2. 思路分析 dp。 开一个dp数组和a数组。dp[i]表示在当前这一格所需要的费用&#xff0c;a数组其实就是题目中的cost数组。 因为最后要求到顶楼的最低费用&a…

Redis: java客户端

文章目录 一、Redis的Java客户端1、Jedis&#xff08;1&#xff09;Jedis操作Redis&#xff08;2&#xff09;Jedis连接池 2、lettuce3、Redisson4、SpringDataRedis客户端&#xff08;1&#xff09;介绍&#xff08;2&#xff09;序列化&#xff08;3&#xff09;StringRedisT…

中国人工智能产业年会智能交通与自动驾驶专题全景扫描

中国人工智能产业年会&#xff08;CAIIAC&#xff09;是中国人工智能技术发展和应用的重要展示平台&#xff0c;不仅关注创新&#xff0c;还涵盖了市场和监管方面的内容&#xff0c;对于促进人工智能领域的发展起到了重要作用。年会汇集了来自学术界、工业界和政府的专家&#…

Python数据可视化:无向网络图

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 Python数据可视化&#xff1a; 无向网络图 [太阳]选择题 关于以下代码输出结果的说法中正确的是? import networkx as nx import matplotlib.pyplot as plt a [(A, B), (B, C), (B, D)] …

基于小程序实现的4s店管理系统

作者主页&#xff1a;Java码库 主营内容&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app等设计与开发。 收藏点赞不迷路 关注作者有好处 文末获取源码 技术选型 【后端】&#xff1a;Java 【框架】&#xff1a;ssm 【…

字体反爬积累知识

目录 一、什么是字体反扒 二、Unicode编码 三、利用font包获取映射关系 一、什么是字体反扒 字体反爬是一种常见的反爬虫技术&#xff0c;它通过将网页中的文本内容转换为特殊的字体格式来防止爬虫程序直接获取和解析文本信息。字体反爬的原理是将常规的字符映射到特殊的字…

MyBatisPlus自定义SQL

✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉🍎个人主页:Leo的博客💞当前专栏: 循序渐进学SpringBoot ✨特色专栏: MySQL学习 🥭本文内容:MyBatisPlus自定义SQL 📚个人知识库: Leo知识库,欢迎大家访问 目录 1.前言☕…

ArcGIS三维景观分层显示

今天将向大家介绍的事在ArcGIS中如何创建多层三维显示。 地表为影像的 地表为地形晕渲的 在土壤分层、油气分层等都有着十分重要的应用。下面我们具体来看看实现过程 一、 准备数据及提取栅格范围 我们这次准备的数据是之前GIS100例-30讲的案例数据。《ArcGIS三维影像图剖面图…

WebRTC直播间搭建记录

考虑到后续增加平台直播的可能性&#xff0c;笔记记录一下WebRTC相关. 让我们分别分析两种情况下的WebRTC连接建立过程&#xff1a; 情况一&#xff1a;AB之间可以直接通信 1.信令交换&#xff1a; 设备A和设备B首先通过信令服务器交换SDP&#xff08;Session Description Pr…

科技驱动未来,提升AI算力,GPU扩展正当时

要说这两年最火的科技是什么&#xff1f;我想“AI人工智能”肯定是最有资格上榜的&#xff0c;尤其ChatGPT推出后迅速在社交媒体上走红&#xff0c;短短5天&#xff0c;注册用户数就超过100万&#xff0c;2023年一月末&#xff0c;ChatGPT的月活用户更是突破1亿&#xff0c;成为…

嵌入式4-16

tftpd #include <myhead.h> #define SER_IP "192.168.125.243" //服务器IP地址 #define SER_PORT 69 //服务器端口号 #define CLI_IP "192.168.125.244" //客户端IP地址 #define CLI_PORT 8889 //客户端端…

C#创建磁性窗体的方法:创建特殊窗体

目录 一、磁性窗体 二、磁性窗体的实现方法 (1)无标题窗体的移动 (2)Left属性 (3)Top属性 二、设计一个磁性窗体的实例 &#xff08;1&#xff09;资源管理器Resources.Designer.cs设计 &#xff08;2&#xff09;公共类Frm_Play.cs &#xff08;3&#xff09;主窗体 …

Java Spring 框架下利用 MyBatis 实现请求 MySQL 数据库的存储过程

Java Spring 框架下利用 MyBatis 实现请求 MySQL 数据库的存储过程 环境准备与前置知识1. 创建 MySQL 存储过程2. 配置数据源3. 创建实体类4. 创建 Mapper 接口5. 创建 Mapper XML 文件6. 创建 Service 接口及Impl实现类7. 创建 Controller 类8. 测试与总结 在现代的 Web 应用开…

STM32 F103 C8T6开发笔记14:与HLK-LD303-24G测距雷达通信

今日尝试配通STM32 F103 ZET6与HLK-LD303-24G测距雷达的串口通信解码 文章提供测试代码...... 目录 HLK-LD303-24G测距雷达外观&#xff1a; 线路连接准备&#xff1a; 定时器与串口配置准备&#xff1a; 定时器2的初始化&#xff1a; 串口1、2初始化&#xff1a; 串口1、2自定…

ARP代理

10.1.0.1/8 和10.2.0.1/8是在同一个网段 10.1.0.2/16 和10.2.0.2/16 不在同一个网段 10.1.0.1/8 和10.1.0.2/16 是可以ping通的 包发出来了&#xff0c;报文有发出来&#xff0c;目的地址是广播包 广播请求&#xff0c;发到路由器的接口G 0/0/0 target不是本接口&#xff0…

【C++学习】C++IO流

这里写目录标题 &#x1f680;C语言的输入与输出&#x1f680;什么是流&#x1f680;CIO流&#x1f680;C标准IO流&#x1f680;C文件IO流 &#x1f680;C语言的输入与输出 C语言中我们用到的最频繁的输入输出方式就是scanf ()与printf()。 scanf(): 从标准输入设备(键盘)读取…

windows网络驱动开发

基石&#xff1a;WFP 1、简介 Windows过滤平台&#xff08;Windows Filtering Platform, WFP&#xff09;&#xff0c;是从Vista系统后新增的一套系统API和服务。开发者可以在WFP框架已划分的不同分层中进行过滤、重定向、修改网络数据包&#xff0c;以实现防火墙、入侵检测系…

pdf做批注编辑工具 最新pdf reader pro3.3.1.0激活版

PDF Reader Pro是一款功能强大的PDF阅读和编辑工具。它提供了多种工具和功能&#xff0c;帮助用户对PDF文档进行浏览、注释、编辑、转换和签名等操作。以下是PDF Reader Pro的一些主要特色&#xff1a; 最新pdf reader pro3.3.1.0激活版下载 多种查看模式&#xff1a;PDF Reade…