Security6.4.2 自定义异常中统一响应遇到的问题

 更新时间:2025年03月14日 11:36:43   作者:阳光晓枫ya  
本文主要介绍了Security6.4.2 自定义异常中统一响应遇到的问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

背景

进行前后端分离开发,在登录认证过程中需要抛出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 自定义异常统一响应内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java 如何判断是否是26个英文字母

    java 如何判断是否是26个英文字母

    这篇文章主要介绍了java 如何判断是否是26个英文字母的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-01-01
  • 理解Java访问权限控制

    理解Java访问权限控制

    这篇文章主要帮助大家深入的理解Java访问权限控制,为何需要访问控制权限,本文给出了解释,感兴趣的小伙伴们可以参考一下
    2016-02-02
  • 基于visualvm监控类实现过程详解

    基于visualvm监控类实现过程详解

    这篇文章主要介绍了基于visualvm监控类实现过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-09-09
  • 详解SpringCloud-OpenFeign组件的使用

    详解SpringCloud-OpenFeign组件的使用

    这篇文章主要介绍了SpringCloud-OpenFeign组件的使用,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-12-12
  • Springboot 2.x RabbitTemplate默认消息持久化的原因解析

    Springboot 2.x RabbitTemplate默认消息持久化的原因解析

    这篇文章主要介绍了Springboot 2.x RabbitTemplate默认消息持久化的原因解析,本文通过示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-03-03
  • Springboot Logback日志多文件输出方式(按日期和大小分割)

    Springboot Logback日志多文件输出方式(按日期和大小分割)

    这篇文章主要介绍了Springboot Logback日志多文件输出方式(按日期和大小分割),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-05-05
  • Mybatis执行流程、缓存原理及相关面试题汇总

    Mybatis执行流程、缓存原理及相关面试题汇总

    最近刚学完MyBatis,趁着大好机会,总结一下它的执行流程,面试也爱问这个,下面这篇文章主要给大家介绍了关于Mybatis执行流程、缓存原理及相关面试题的相关资料,需要的朋友可以参考下
    2022-02-02
  • 详解Java中的do...while循环语句的使用方法

    详解Java中的do...while循环语句的使用方法

    这篇文章主要介绍了Java中的do...while循环语句的使用方法,是Java入门学习中的基础知识,需要的朋友可以参考下
    2015-10-10
  • 详解SpringBoot 解决拦截器注入Service为空问题

    详解SpringBoot 解决拦截器注入Service为空问题

    这篇文章主要介绍了详解SpringBoot 解决拦截器注入Service为空问题的解决,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-06-06
  • resttemplate设置params的方法

    resttemplate设置params的方法

    RestTemplate设置请求参数的方式根据请求类型(GET/POST)和参数形式(路径参数、查询参数、JSON请求体)有所不同,下面通过本文给大家介绍resttemplate设置params的方法,感兴趣的朋友一起看看吧
    2025-04-04

最新评论