说明

SpringSecurity的异常过滤器ExceptionTranslationFilter默认在倒数第二位出现,即过滤器即将走完的时候出现,不知道SpringSecurity为啥这样实现,但是这样处理的话就会导致前面过滤器中的异常无法处理到。
最终的结果就是对于非认证异常等就会走默认的实现,即重定向到/error页面,但前后端分离项目中我们又没有/error页面,因此又会出现一个循环异常,导致报错到前端的异常为InsufficientAuthenticationException,这个异常是很隐晦的,基本看不出来本质的异常信息,毕竟已经重定向了N次了。
因此前后端分离项目中,我们需要第一次报错出去就返回异常信息。

实现

方案一: SpringSecurity中加入过滤器链

我们需要在Config配置中添加过滤器,在WebAsyncManagerIntegrationFilter之前添加

1
2
3
4
protected void configure(HttpSecurity http) throws Exception {
//配置信息中添加过滤器
http.addFilterBefore(new ExceptionTranslationFilter(exceptionResolverHandler), WebAsyncManagerIntegrationFilter.class)
}

未添加之前的过滤器链为:

1
2
3
4
5
6
7
8
9
10
11
12
13
1 = {WebAsyncManagerlntegrationFilter@9302} 
2 = {SecurityContextPersistenceFilter@9303}
3 = {HeaderWriterFilter@9288}
4= {LogoutFilter@9304}
5 = {JsonLoginFilter@9305}
6 = {AuthUserContextFilter@9306}
7 = {ConcurrentSessionFilter@9307}
8 = {RequestCacheAwareFilter@9308}
9 = {SecurityContextHolderAwareRequestFilter@9309}
10 = {AnonymousAuthenticationFilter@9310}
11 = {SessionManagementFilter@9311}
12 = {ExceptionTranslationFilter@9312} // 异常处理过滤器
13 = {FilterSecuritylnterceptor@9313}

添加之后的过滤器链为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1 = {ExceptionTranslationFilter@9304}    //异常处理过滤器
2 = {ExceptionTranslationFilter@9306} //异常处理过滤器
3 = {WebAsyncManagerlntegrationFilter@9307}
4 = {SecurityContextPersistenceFilter@9308}
5 = {HeaderWriterFilter@9291}
6 = {LogoutFilter@9309}
7 = {JsonLoginFilter@9310}
8 = {AuthUserContextFilter@9311}
9 = {ConcurrentSessionFilter@9312}
10 = {RequestCacheAwareFilter@9313}
11 = {SecurityContextHolderAwareRequestFilter@9314)
12 = {AnonymousAuthenticationFilter@9315}
13 = {SessionManagementFilter@9316)
14 = {FilterSecuritylnterceptor@9317}

这里有一个问题,就是可以看见最后一个异常过滤器前移了,移动到了WebAsyncManagerlntegrationFilter之前,但是为啥会移动呢,猜测大概在配置的时候addFilterBefore添加ExceptionTranslationFilter时就会调整ExceptionTranslationFilter的位置,就出现了两个

因此,建议咱们还是自己写一个过滤器处理

方案二: 在SpirngSecurity过滤器前添加过滤器链

SpringSecurity的过滤器链其实也是基于Filter实现的,在它自己的filter中维护了一系列的security过滤器逐一调用,最后调用实际的方法
那么我们实现的时候可以在其之前添加一个保底的过滤器,即当SpringSecurity没有完成处理任何一个异常的时候就返回此过滤器
此处需要注意什么Security什么时候重定向到错误页面的,但是测试发现并没有重定向,所以先就这样

经测试SpringSecurity的过滤器的优先级为-100因此我们配置的优先级要小于100,我们这里直接配置-101即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48


/**
* 全局异常处理过滤器,在SpringSecurity的过滤器链之前执行,
* @author kewen
* @since 2024-07-10
*/
@Order(-101)
public class BeforeSecurityFilter extends OncePerRequestFilter {

HandlerExceptionResolver handlerExceptionResolver;

public BeforeSecurityFilter(HandlerExceptionResolver handlerExceptionResolver) {
this.handlerExceptionResolver = handlerExceptionResolver;
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String requestURI = request.getRequestURI();
if (requestURI.startsWith("/error")) {
resolver(request,response,new RuntimeException("没有此页面 /error"));
}
try {
filterChain.doFilter(request, response);
} catch (ServletException e) {
throw new RuntimeException(e);
} catch (Exception ex){
resolver(request,response,ex);
} finally {
logger.warn("security前处理异常");
}
}
private void resolver(HttpServletRequest request, HttpServletResponse response, Exception ex){
ModelAndView modelAndView = handlerExceptionResolver.resolveException(request, response, null, ex);
if (modelAndView == null) {
logger.warn("返回无处理解析器,按照默认返回");
response.setContentType("application/json;charset=utf-8");
try {
PrintWriter out = response.getWriter();
out.write("返回异常,且没有配置返回解析,异常信息:" + ex.getMessage());
out.flush();
out.close();
} catch (IOException e) {
throw new AuthenticationServiceException("BeforeSecurityFilter 解析返回异常", e);
}
}
}
}

然后在配置中配置Bean即可

1
2
3
4
@Bean
BeforeSecurityFilter beforeSecurityFilter(HandlerExceptionResolver handlerExceptionResolver){
return new BeforeSecurityFilter(handlerExceptionResolver);
}

这样就直接保底执行异常