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

devtools/2024/9/22 21:08:16/

前言:

在改造老项目登录功能的时候,使用了过滤器对 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.ppmy.cn/devtools/7548.html

相关文章

使用Python实现文本分类与情感分析模型

文本分类与情感分析是自然语言处理中常见的任务&#xff0c;它们可以帮助我们对文本进行自动分类和情感判断。在本文中&#xff0c;我们将介绍文本分类与情感分析的基本原理和常见的实现方法&#xff0c;并使用Python来实现这些模型。 什么是文本分类与情感分析&#xff1f; …

python-基础篇-列表和元组-脚本-结合生活-第一、基础

文章目录 一、基础6.1 管理QQ好友6.2 添加和删除QQ好友6.3 模拟QQ好友添加辅助信息6.4 统计QQ好友并排序6.5 为歌曲库添加歌手信息6.6 世界那么大&#xff0c;我想去看看6.7 输入输出杭黄高铁的沿线高铁站6.8 模拟车牌号码选择6.9 模拟淘宝的菜单组合功能6.10 模拟重庆江北机场…

基于unity+c#的随机点名系统(简单UI界面+列表+数组)

目录 一、功能界面显示 二、UI 1、视频的使用 &#xff08;1&#xff09;渲染纹理 &#xff08;2&#xff09; 视频铺全屏 &#xff08;3&#xff09;视频的调用 2、 下拉文本框的使用&#xff08;旧版&#xff09; 3、输入文本框的使用&#xff08;旧版&#xff09; …

Git学习笔记(二)Git安装及基础命令

前面的文章中&#xff0c;我们已经对Git的一些基础知识进行了简单的介绍&#xff0c;包括它的作用&#xff0c;Git组件&#xff0c;文件状态以及一些简单的命令介绍等等。那么这一章主要介绍如何下载安装配置Git&#xff0c;以及Git的一些常用命令和实操截图。 Git下载与安装 …

23.组件注册方式

组件注册方式 一个 Vue 组件在使用前需要先被“注册”&#xff0c;这样 Vue 才能在渲染模板时找到其对应的实现。组件注册有两种方式&#xff1a;全局注册和局部注册 全局注册 import { createApp } from vue import App from ./App.vue import GlobalComponent from ".…

Java中关键字this的妙用:调用方法、属性与构造器

Java中关键字this的妙用&#xff1a;调用方法、属性与构造器 在Java编程中&#xff0c;this是一个非常重要的关键字&#xff0c;它指代当前对象。通过this&#xff0c;我们可以方便地引用当前对象的属性和方法&#xff0c;或者在构造器中调用另一个构造器。对于初学者来说&…

Biome 1.7 发布,支持从 ESLint 和 Prettier 迁移

近日&#xff0c;Biome v1.7 正式发布&#xff01;这个新版本提供了从 ESLint 和 Prettier 迁移的简单路径。它还引入了格式化程序和 linter 的实验性机器可读报告、新的 linter 规则和许多修复。 使用以下命令更新 Biome&#xff1a; npm install --save-dev --save-exact b…

K8s ingress-controller中nginx文件上传大小的限制

# 20、K8s ingress-controller中nginx文件上传大小的限制 问题&#xff1a;1.应用程序中上传文件文件出错&#xff0c;页面提示“您上传的文件太大了&#xff0c;请压缩图片后重试。” 2.通过F12 可以看到&#xff0c;后台提示 403错误&#xff0c;可以看到出错是由于nginx的限…