在UsernamePasswordAuthenticationFilter中抛自定义AuthenticationException未生效问题
问题描述
我定义了继承自AuthenticationException的自定义异常UnauthorizedException,在自定义UsernamePasswordAuthenticationFilter的attemptAuthentication方法中,捕获各类认证相关异常后抛出该自定义异常(携带如“用户名或密码错误”等自定义消息)。但在自定义AuthenticationEntryPoint的commence方法中打印异常消息时,却始终输出InsufficientAuthenticationException: Full authentication is required to access this resource,无论用户名是否存在于数据库中均是如此。请问遗漏了什么配置或逻辑?
问题原因及解决办法
异常被默认处理流程覆盖
UsernamePasswordAuthenticationFilter的父类AbstractAuthenticationProcessingFilter的doFilter方法会捕获attemptAuthentication抛出的异常,调用默认的unsuccessfulAuthentication方法。这个默认逻辑会清除认证上下文,并且可能将自定义异常替换为默认的InsufficientAuthenticationException,导致最终传递到AuthenticationEntryPoint的不是你定义的异常。解决:重写自定义Filter的
unsuccessfulAuthentication方法,直接将捕获到的原始异常传递给AuthenticationEntryPoint,示例代码:@Override protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { SecurityContextHolder.clearContext(); // 直接传入原始异常,跳过默认封装逻辑 getAuthenticationEntryPoint().commence(request, response, failed); }自定义Filter未替换默认Filter
如果只是额外添加了自定义的UsernamePasswordAuthenticationFilter,而没有替换Spring Security默认的同名Filter,那么默认Filter会先处理请求,抛出默认异常,你的自定义异常根本不会被触发。解决:在Spring Security配置类中,使用
addFilterAt方法将自定义Filter替换默认Filter,示例代码:@Override protected void configure(HttpSecurity http) throws Exception { http // 用自定义Filter替换默认的UsernamePasswordAuthenticationFilter .addFilterAt(customUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) .exceptionHandling() // 配置自定义的AuthenticationEntryPoint .authenticationEntryPoint(customAuthenticationEntryPoint()) // 其他安全配置... }未正确解析嵌套异常
部分场景下,自定义异常可能被包装在其他异常容器中,导致commence方法拿到的是外层异常而非你抛出的UnauthorizedException。解决:在
AuthenticationEntryPoint的commence方法中添加异常拆解逻辑,获取原始自定义异常:@Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { Throwable targetException = authException; // 逐层拆解,找到最内层的自定义异常 while (targetException != null && !(targetException instanceof UnauthorizedException)) { targetException = targetException.getCause(); } // 优先使用自定义异常的消息,否则用默认消息 String errorMsg = targetException instanceof UnauthorizedException ? ((UnauthorizedException) targetException).getMessage() : authException.getMessage(); // 输出或返回错误消息 response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.getWriter().write(errorMsg); }
内容的提问来源于stack exchange,提问作者moze

