Spring Security 异常统一返回设计实践

216 阅读2分钟

🤔 为什么要统一异常返回?

Spring Security 默认的异常返回行为:

  • 登录失败?返回一堆英文堆栈;
  • 没权限访问?直接 403 Forbidden,JSON 都不是;
  • Token 失效?跳转登录页;
  • 接口出错?前端无法处理异常信息……

对于前后端分离项目来说,简直是灾难!

我们希望:

  • ✅ 所有异常统一返回 JSON;
  • ✅ 状态码清晰(401、403、500);
  • ✅ 响应格式统一,前端易处理;
  • ✅ 错误信息可自定义提示,方便用户与开发定位问题。

🧱 一、定义统一响应结构

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ApiResponse<T> {
    private int code;
    private String message;
    private T data;

    public static <T> ApiResponse<T> fail(int code, String message) {
        return new ApiResponse<>(code, message, null);
    }
}

🔐 二、自定义异常处理组件

1️⃣ 未登录:认证失败处理器

实现 AuthenticationEntryPoint 接口:

@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request,
                         HttpServletResponse response,
                         AuthenticationException authException) throws IOException {
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);

        ApiResponse<?> result = ApiResponse.fail(401, "未登录或登录过期,请重新登录");
        response.getWriter().write(new ObjectMapper().writeValueAsString(result));
    }
}

2️⃣ 权限不足:访问被拒绝处理器

实现 AccessDeniedHandler 接口:

@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request,
                       HttpServletResponse response,
                       AccessDeniedException accessDeniedException) throws IOException {
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);

        ApiResponse<?> result = ApiResponse.fail(403, "权限不足,无法访问此资源");
        response.getWriter().write(new ObjectMapper().writeValueAsString(result));
    }
}

🔧 三、注册到 Security 配置中

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http,
                                           CustomAuthenticationEntryPoint entryPoint,
                                           CustomAccessDeniedHandler accessDeniedHandler) throws Exception {
        http
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/login", "/public/**").permitAll()
                .anyRequest().authenticated()
            )
            .exceptionHandling(exception -> exception
                .authenticationEntryPoint(entryPoint)
                .accessDeniedHandler(accessDeniedHandler)
            );

        return http.build();
    }
}

✅ 效果验证

场景HTTP 状态码JSON 响应内容
未登录访问401{"code":401,"message":"未登录或登录过期"}
权限不足403{"code":403,"message":"权限不足"}
登录失败(扩展)401{"code":401,"message":"用户名或密码错误"}

✍️ 总结

通过实现:

  • AuthenticationEntryPoint → 处理未登录异常
  • AccessDeniedHandler → 处理权限不足异常
  • 配置进 HttpSecurity 的异常处理链路

你就可以实现一套 规范、前后端友好、可维护性强 的 Spring Security 异常返回机制。