1. 概述

本文将演示如何在 Spring Boot 项目中自定义 Spring Security 的认证失败处理逻辑。目标是通过表单登录(form login)方式实现用户认证,并在登录失败时返回结构化错误信息,而不是跳转到默认错误页面。

如果你对 Spring Security 和 Spring Boot 中的表单登录还不熟悉,建议先阅读 Spring Boot 安全自动配置Spring Security 登录机制 两篇文章。

2. 认证与授权

认证(Authentication)和授权(Authorization)虽然经常一起出现,但它们职责分明:

  • 认证:确认“你是谁”。发生在授权之前,核心是校验用户名和密码是否匹配系统中存储的凭证。
  • 授权:确认“你能做什么”。在用户身份合法的前提下,判断其是否有权限访问某个接口或资源。

本文聚焦于认证失败的处理。授权失败我们另文再讲。

3. Spring Security 的 AuthenticationFailureHandler

Spring Security 默认提供了一套认证失败处理机制——通常是重定向回登录页,并附带一个错误参数(如 ?error)。但在前后端分离或 API 场景下,这种跳转显然不合适。

3.1 自定义失败处理器

我们可以通过实现 AuthenticationFailureHandler 接口,完全接管认证失败后的逻辑。以下是一个返回 JSON 响应的自定义处理器:

public class CustomAuthenticationFailureHandler 
  implements AuthenticationFailureHandler {

    private ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public void onAuthenticationFailure(
        HttpServletRequest request,
        HttpServletResponse response,
        AuthenticationException exception) 
        throws IOException, ServletException {

        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        Map<String, Object> data = new HashMap<>();
        data.put("timestamp", Calendar.getInstance().getTime());
        data.put("exception", exception.getMessage());

        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(objectMapper.writeValueAsString(data));
    }
}

⚠️ 注意:

  • 设置状态码为 401 Unauthorized
  • 显式设置 Content-Typeapplication/json,避免前端解析出错
  • 使用 getWriter() 而非 getOutputStream() 更适合写字符串

3.2 Spring 内置的 FailureHandler

除了自定义,Spring Security 也提供了几个开箱即用的实现,按需选用:

实现类 说明
SimpleUrlAuthenticationFailureHandler 默认实现,可配置 failureUrl 进行重定向,否则返回 401
ForwardAuthenticationFailureHandler 直接服务器内部 forward 到指定 URL
ExceptionMappingAuthenticationFailureHandler 根据异常类型映射到不同跳转地址,适合精细化错误处理
DelegatingAuthenticationFailureHandler 支持为不同 AuthenticationException 子类注册不同的处理器,灵活性最高

3.3 配置自定义处理器

接下来,在安全配置中替换默认处理器:

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

    @Bean
    public InMemoryUserDetailsManager userDetailsService() {
        UserDetails user1 = User.withUsername("user1")
            .password(passwordEncoder().encode("user1Pass"))
            .roles("USER")
            .build();
        return new InMemoryUserDetailsManager(user1);
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authz -> authz
                .anyRequest().authenticated()
            )
            .formLogin(form -> form
                .failureHandler(authenticationFailureHandler()) // ⬅️ 关键:注入自定义处理器
            );
        return http.build();
    }

    @Bean
    public AuthenticationFailureHandler authenticationFailureHandler() {
        return new CustomAuthenticationFailureHandler();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

✅ 关键点:

  • 使用 .failureHandler() 显式指定处理器 Bean
  • 现代 Spring Security(6+)推荐使用 Lambda 风格配置,更清晰
  • InMemoryUserDetailsManager 仅用于演示,生产环境应使用数据库或 LDAP

4. 总结

通过实现 AuthenticationFailureHandler,我们可以轻松控制认证失败后的响应行为。无论是返回 JSON、记录日志、触发风控,还是结合限流策略,都可以在这里统一处理。

这种“插件式”扩展机制是 Spring Security 的精髓之一,理解它有助于我们构建更健壮的安全体系。

项目完整代码见 GitHub:https://github.com/eugenp/tutorials/tree/master/spring-security-modules/spring-security-web-login

本地运行后可通过 http://localhost:8080 访问测试。踩坑提示:确保登录请求是 POST,且包含正确的 usernamepassword 参数。


原始标题:Spring Security Custom AuthenticationFailureHandler