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

news2025/2/28 20:17:43

前言:

在改造老项目登录功能的时候,使用了过滤器对 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)的区别】

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

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

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

相关文章

OpenStack云平台实战

1、环境准备 主机CPU数量内存硬盘IPV4发行版controller48GB100GBens33: 192.168.110.27/24 esn34: 192.168.237.131/24CentOS 7.9compute48GB200GB、100GBens33: 192.168.110.26/24 esn34: 192.168.237.132/24CentOS 7.9 1.1 虚拟机安装部署 1.1.1 创建虚拟机 这里16或者17都…

机器学习——模型评价

概述 在机器学习中&#xff0c;模型评价是评估和比较不同模型性能的关键步骤之一。它是通过对模型的预测结果与真实标签进行比较&#xff0c;从而量化模型的预测能力、泛化能力和稳定性。模型评价旨在选择最佳的模型&#xff0c;理解模型的行为&#xff0c;并为模型的改进提供…

c++11 标准模板(STL)本地化库 - 平面类别 - 在字符编码间转换,包括 UTF-8、UTF-16、UTF-32 (七)

本地化库 本地环境设施包含字符分类和字符串校对、数值、货币及日期/时间格式化和分析&#xff0c;以及消息取得的国际化支持。本地环境设置控制流 I/O 、正则表达式库和 C 标准库的其他组件的行为。 平面类别 在字符编码间转换&#xff0c;包括 UTF-8、UTF-16、UTF-32 std::…

03.卸载MySQL

卸载MySQL 1.Windows卸载MySQL8 停止服务 用命令停止或者在服务中停止都可以 net stop mysql&#xff08;服务名字可以去服务里面看一下&#xff09;控制面板卸载MySQL 卸载MySQL8.0的程序可以和其他桌面应用程序一样直接在控制面板选择卸载程序&#xff0c;并在程序列表中…

nacos配置mysql(windows)

nacos默认是使用的内置数据库derby ,可通过配置修改成mysql,修改成mysql之后&#xff0c;之前配置在derby的数据会丢失 本文使用mysql版本为8.0.22 nacos版本为2.3.1 在mysql里面先创建一个数据库test(名称自定义&#xff0c;和后面配置文件里面的一样就好了) 在上面创建的数据…

【学习笔记】Python大数据处理与分析——pandas数据分析

一、pandas中的对象 1、Series对象 由两个相互关联的数组(values, index)组成&#xff0c;前者&#xff08;又称主数组&#xff09;存储数据&#xff0c;后者存储values内每个元素对应关联的标签。 import numpy as np import pandas as pds1 pd.Series([1, 3, 5, 7])print(…

数据结构排序算法

排序也称排序算法(SortAlgorithm)&#xff0c;排序是将一组数据&#xff0c;依指定的顺序进行排列的过程。 分类 内部排序【使用内存】 指将需要处理的所有数据都加载到内部存储器中进行排序插入排序 直接插入排序希尔排序 选择排序 简单选择排序堆排序 交换排序 冒泡排序快速…

【随笔】Git 高级篇 -- 远程仓库提交本地记录 git push(三十)

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

大学生前端学习第一天:了解前端

引言&#xff1a; 哈喽&#xff0c;各位大学生们&#xff0c;大家好呀&#xff0c;在本篇博客&#xff0c;我们将引入一个新的板块学习&#xff0c;那就是前端&#xff0c;关于前端&#xff0c;GPT是这样描述的&#xff1a;前端通常指的是Web开发中用户界面的部分&#xff0c;…

35. UE5 RPG制作火球术技能

接下来&#xff0c;我们将制作技能了&#xff0c;总算迈进了一大步。首先回顾一下之前是如何实现技能触发的&#xff0c;然后再进入正题。 如果想实现我之前的触发方式的&#xff0c;请看此栏目的31-33篇文章&#xff0c;讲解了实现逻辑&#xff0c;这里总结一下&#xff1a; …

用于半监督的图扩散网络 笔记

1 Title Graph Neural Diffusion Networks for Semi-supervised Learning&#xff08;Wei Ye, Zexi Huang, Yunqi Hong, and Ambuj Singh&#xff09;【2022】 2 Conclusion This paper proposes a new graph neural network called GND-Nets (for Graph Neural Diffu…

1W 6KVDC 隔离双输出 DC/DC 电源模块 ——TPJ 系列

TPJ一款有超高隔离电压的电源模块&#xff0c;主要用于医疗仪器和设备&#xff0c;特别在安全设备的应用中起着相当重要的作用&#xff0c; 它的绝缘设计完全能满足对隔离电压要求超过6000V的应用&#xff0c;在额定负载1W的情况下&#xff0c;工作温度范围为–40℃到 105℃&am…

转换为elementUI提示方法为uni-app的showToast提示

// 转换为elementUI提示方法为uni-app的showToast提示---------------------------------------- // 一般提示 Vue.prototype.$message function(title) {title && uni.showToast({icon: none,title}); }; // 成功提示 Vue.prototype.$message.success (title) > …

项目管理利器 Git

一、序言 今天聊聊 Git。 二、开发的问题 在开发项目时&#xff0c;我们的代码都是直接放在本地的机器上的。如果本地机器出现了问题&#xff0c;怎么办&#xff1f;在企业中&#xff0c;开发项目都是团队协作&#xff0c;一个团队共同维护一个项目该如何处理&#xff1f;团…

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

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

rk3588 安卓调试

rknn装上了android系统&#xff0c;用type-c usb连接上电脑&#xff0c;设备管理器发现了rk3588&#xff0c;但是Android Studio没有发现设备 后来怀疑是驱动没有安装&#xff0c;我用的驱动下载地址&#xff1a; 瑞芯微Rockchip驱动安装助手(适用于RK3308 RK3399等) Mcuzone…

SSH协议的优缺点

SSH&#xff08;Secure Shell&#xff09;是一种用于在计算机网络上进行安全远程访问和执行命令的协议。提供加密通信通道&#xff0c;防止敏感信息在传输过程中被窃听或篡改。SSH还支持文件传输和端口转发等功能&#xff0c;使其成为广泛使用的安全远程管理工具。 1. 安全远程…

Centos7下载配置jdk18与maven3.9.6【图文教程】

个人记录 进入目录 cd /usr/local/JDK下载与配置 OpenJDK官网 下载安装 wget https://download.java.net/openjdk/jdk18/ri/openjdk-1836_linux-x64_bin.tar.gz解压 tar -zxvf openjdk-1836_linux-x64_bin.tar.gz ls ls jdk-18/编辑配置文件 vim /etc/profile配置环境变…

YOLOv8 目标检测项目实操

一 yolov8 背景介绍 YOLOv8是一种尖端的、最先进的(SOTA)模型&#xff0c;建立在以前 YOLO 版本的成功基础上&#xff0c;并引入了新的特性和改进&#xff0c;以进一步提高性能和灵活性。YOLOv8被设计为快速、准确、易于使用&#xff0c;这使它成为一个很好的选择&#xff0c;…

如何在企业微信中更换新的企业主体

企业微信变更主体有什么作用&#xff1f; 做过企业运营的小伙伴都知道&#xff0c;很多时候经常会遇到现有的企业需要注销&#xff0c;切换成新的企业进行经营的情况&#xff0c;但是原来企业申请的企业微信上面却积累了很多客户&#xff0c;肯定不能直接丢弃&#xff0c;所以这…