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 自定义异常统一响应内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Atomikos + MybatisPlus解决多数据源事务一致性问题解决

    Atomikos + MybatisPlus解决多数据源事务一致性问题解决

    在实际项目的开发过程中,我们经常会遇到在同一个项目或微服务中牵涉到使用两个或多个数据源的,本文主要介绍了Atomikos + MybatisPlus解决多数据源事务一致性问题解决,具有一定的参考价值,感兴趣的可以了解一下
    2024-07-07
  • Java设计模式之桥接模式的示例详解

    Java设计模式之桥接模式的示例详解

    桥梁模式是对象的结构模式。又称为柄体(Handle and Body)模式或接口(Interface)模式。本文将通过示例来详细讲解一下这个模式,感兴趣的可以学习一下
    2022-02-02
  • Java代码实现简单酒店管理系统

    Java代码实现简单酒店管理系统

    这篇文章主要为大家详细介绍了Java代码实现简单酒店管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-06-06
  • SpringBoot+MyBatis简单数据访问应用的实例代码

    SpringBoot+MyBatis简单数据访问应用的实例代码

    这篇文章主要介绍了SpringBoot+MyBatis简单数据访问应用的实例代码,需要的朋友可以参考下
    2017-05-05
  • Java如何通过反射方式生成数据库实体类

    Java如何通过反射方式生成数据库实体类

    这篇文章主要介绍了Java如何通过反射方式生成数据库实体类问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-12-12
  • java实现163邮箱发送邮件到qq邮箱成功案例

    java实现163邮箱发送邮件到qq邮箱成功案例

    这篇文章主要为大家分享了java实现163邮箱发送邮件到qq邮箱成功案例,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-05-05
  • MyBatis-Plus Page 分页不生效的问题解决

    MyBatis-Plus Page 分页不生效的问题解决

    分页是常见的一种功能,本文主要介绍了MyBatis-Plus Page分页不生效的问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-07-07
  • Java查找并高亮PDF文本过程解析

    Java查找并高亮PDF文本过程解析

    这篇文章主要介绍了Java查找并高亮PDF文本过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-08-08
  • 解决mybatis返回boolean值时数据库返回null的问题

    解决mybatis返回boolean值时数据库返回null的问题

    这篇文章主要介绍了解决mybatis返回boolean值时数据库返回null的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-11-11
  • idea-java序列化serialversionUID自动生成方式

    idea-java序列化serialversionUID自动生成方式

    Java的Serializable接口用于实现对象的序列化和反序列化,通过将对象转换为字节流来存储或传输,实现Serializable接口的类需要定义serialVersionUID以保证序列化和反序列化过程的兼容性,IDEA提供了便捷的配置和快捷键来生成serialVersionUID
    2025-11-11

最新评论