Security6.4.2 自定义异常中统一响应遇到的问题
作者:阳光晓枫ya
背景
进行前后端分离开发,在登录认证过程中需要抛出token异常,但是异常被servlet捕获并打印在控制台,而前端返回 "Full authentication is required to access this resource"(访问此资源需要完全认证)。
解决办法
在自定义的过滤器里打断点,查看该异常所在的过滤器是否在ExceptionTranslationFilter这个过滤器的后面,如果不在,则使用
.addFilterAfter(异常所在的过滤器, ExceptionTranslationFilter.class)
问题解决~
一、理想情况
在登录认证处理过程中一般都会涉及到Token的处理,比如Token过期、错误等。在正常的处理流程中我们应该是在新建一个JwtFillter类来重写OncePerRequestFilter的doFilterInternal方法。比如:
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 令牌验证
final String token = request.getHeader("Authorization");
final String jwt;
if (StringUtils.isEmpty(token)){
throw new BadCredentialsException("Token无效");
}
}由于security默认屏蔽UsernameNotFoundException并将其转换成BadCredentialsException处理,为了安全考虑,建议使用BadCredentialsException来抛给security处理。
然后在SecurityConfig类中配置过滤器链,如:
@Configuration
public class SecurityConfiguration {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public JwtFillter jwtFillter() {
return new JwtFillter();
}
@Bean
public AuthenticationEntryPoint myAuthenticationEntryPoint() {
return new MyAuthenticationEntryPoint();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// 自定义配置
http.authorizeHttpRequests((requests -> requests
// .requestMatchers("/product/list").hasAuthority("PRODUCT_LIST")
// .requestMatchers("/product/save").hasAuthority("PRODUCT_SAVE")
// .requestMatchers("/product/list").hasRole("USER")
// .requestMatchers("/product/save").hasRole("ADMIN")
.requestMatchers("/user/info").hasRole("ADMIN")
.anyRequest().authenticated())
)
.addFilterBefore(jwtFillter(), AuthenticationFilter.class)
.exceptionHandling(exception ->{
exception.authenticationEntryPoint(myAuthenticationEntryPoint());
})
.csrf(AbstractHttpConfigurer::disable)
.formLogin(AbstractHttpConfigurer::disable);
// 返回新的过滤器链
return http.build();
}
}由于我禁用了formLogin登录表单,与之相关的UsernamePasswordAuthenticationFilter等过滤器会从过滤器链中移除,所以一般都会设置为在 AuthenticationFilter之前( AuthenticationFilter是最后一道过滤器)
为了实现前后端分离,要根据发生的异常告诉前端如何处理,所以还需要自定义一个AuthenticationEntryPoint认证异常处理器(授权异常处理器的逻辑相同)并将其加入过滤器链中,我的自定义认证异常处理类如下:
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
// 向前端响应数据
ResponseUtil.print(response,Result.error(authException.getMessage()));
}
}(此处ResponseUtil和Result为我自定义的工具类,与本次事件无关)
然后在这里进行异常处理逻辑,通过authException获取异常信息,然后就能正常将json格式的响应信息发送给前端。
二、发生异常
但是当运行代码后,得到的响应结果却与预期不符

而后端控制台也打印了一堆错误栈信息

很明显,该BadCredentialsException异常本应该被security捕获,结果居然被servlet容器给捕获了。仔细检查代码后能确定除了SecurityConfig以外都没有问题,那大概率是过滤器顺序有问题。找了很多资料都没有相应的解决办法(可能是我找的还不够多[doge])。
三、异常原因
既然是过滤器顺序有问题,那就看一下过滤器链吧(可恶,想了好久才想起来这一点)

在JwtFillter(你自己的jwt过滤器)里设置断点,查看filterChain里的过滤器顺序,发现JwtFillter在中间的位置,而ExceptionTranslationFilter和AuthorizationFilter在最后面,我还以为
.addFilterBefore(jwtFillter(), AuthenticationFilter.class)
这段代码是将我的过滤器放在AuthorizationFilter的前一个位置呢,结果跑那么前面去了。然后再来看一下ExceptionTranslationFilter是如何处理认证异常的:
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
chain.doFilter(request, response);// 此处对后续的链路进行异常捕获
} catch (IOException var7) {
throw var7;
} catch (Exception var8) {
Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(var8);
RuntimeException securityException = (AuthenticationException)this.throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain);
if (securityException == null) {
securityException = (AccessDeniedException)this.throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
}
if (securityException == null) {
this.rethrow(var8);
}
if (response.isCommitted()) {
throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", var8);
}
this.handleSpringSecurityException(request, response, chain, (RuntimeException)securityException);
}
}可以看到,该过滤器是对后续的链路进行异常捕获,所以自定义的JwtFilter应当放在ExceptionTranslationFilter的后面,即
.addFilterAfter(jwtFillter(), ExceptionTranslationFilter.class)
于是,该异常便能正确被security捕获并发送给自定义认证异常处理器进行处理。所以写过滤器的时候一定要注意检查链路顺序啊[吐血]
不过我跟着视频学习的时候,对方并没有设置这个也能在自定义异常处理器中获取异常信息,就很奇怪.....估计是Security的版本不同导致的吧
到此这篇关于Security6.4.2 自定义异常中统一响应遇到的问题的文章就介绍到这了,更多相关Security6.4.2 自定义异常统一响应内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
