0%

Filter无法进入全局异常捕获

Filter无法进入全局异常捕获

问题

​ 我接手的一个服务没有依赖于公司建设项目一体化体系中的认证中心,导致这个服务自身需要支持认证授权模块,由于需求上只需要一个简单的登录模块,所以引入了spring-security模块来进行认证授权相关处理。早上收到一个测试提出的bug,服务在使用一段时间不使用后,再操作,会出现警告异常提示。定位问题发现是由于token失效导致后端校验报错,由于这个校验是在filter中处理,导致全局异常无法捕获,前端获取的异常结构与标准结构(约定异常体结构<msg,code,info>等)不符,导致前端也无法解析具体异常信息,所以界面上显示的是直接抛出的异常信息。

​ 为什么filter中抛出的异常无法被全局异常处理器捕获?这是因为安全校验模块,不论是spring-security或者shiro这类,都是基于javax.servlet.filter过滤器来拦截请求,进行认证处理。而全局异常处理器@ControllerAdvice是对Controller控制层进行拦截,也就是说请求都还没有到达控制层已经抛出异常了,所以全局异常处理器无法捕获。

解决

​ 我查询了一些资料,大多数人都从两个解决方案出发解决这个问题。

方案一:filter中直接将异常信息写到response流中。这里有一个小细节,我个人建议具体的异常信息还是由全局异常处理器生成,而不是自己通过map或者异常类手动生成一个异常对象,虽然说一般情况下,异常结构与前端有约定不会随意更改,但是如果出现非一般情况,变更了异常结构,但filter中的异常没有相应更改,出现的问题往往是致命的。

1
2
3
4
5
6
7
8
9
10
11
12
private void responseErrorHandle(HttpServletResponse response, BusinessException businessException){
try{
ErrorResponse<?> result = globalDefaultExceptionHandler.processBusinessException(businessException, response);
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.write(result);
out.flush();
out.close();
}catch (Exception e){
log.error(e.getMessage(), e);
}
}

方案二 :通过转发到控制层,再由控制层抛出具体的异常信息。(这个方案我个人不推荐)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 在filter中如果出现异常,通过getRequestDispatcher转发到抛出异常的控制层接口中。
try {
request.getRequestDispatcher("/common/throwException").forward(request, response);
} catch (Exception e) {
e.printStackTrace();
}

@GetMapping("/getThrowException")
@IgnoreAuth
public Result<String> getThrowException(@RequestParam String code) {
throw BusinessException.buildAlter(ErrorEnum.VERIFICATION_CREATE_ERROR);
}

@PostMapping("/postThrowException")
@IgnoreAuth
public Result<String> postThrowException(@RequestParam String code) {
throw BusinessException.buildAlter(ErrorEnum.VERIFICATION_CREATE_ERROR);
}

@RequestMapping("/throwException")
@IgnoreAuth
public Result<String> throwException(@RequestParam String code) {
throw BusinessException.buildAlter(ErrorEnum.VERIFICATION_CREATE_ERROR);
}

我不推荐这个方案的原因是:

  1. 通过getRequestDispatcher().forward()这个是请求转发,在filter中无法设置请求参数,而一般应用系统自定义的异常信息不只是只有提示信息,有时也伴随着一下其他参数,比如异常编码、异常等级、告警方式等等,这些内容由于无法通过parameter传递,所以只能通过restful风格,通过地址栏参数来进行传递,比如/common/throwException/{code},这种方式传参比较有限,且后期可能不容易扩展。
  2. 由于是转发请求,所以请求方式取决于原请求,也就是假设浏览器(客户端)发起的请求是post,那么转发的请求就是post;如果浏览器发起的请求是get,那么转发的请求就是get。这就转发的目标控制层接口,不能使用@PostMapping或者GetMapping单一接口来标记,而必须使用RequestMapping,且设置method属性值为getpost数组。

综上,我比较推荐方案一的方式进行filter中的异常信息抛出,并且尽可能通过全局异常处理器来生成对应的异常结构,便于扩展。

-------------本文结束感谢您的阅读-------------