You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot抛出ResponseStatusException为何始终返回403错误?

问题分析与解决方案

核心原因

你的问题出在Spring Boot的错误处理端点被Spring Security拦截:
当业务代码抛出ResponseStatusException时,Spring Boot会自动将请求转发到内置的/error端点,用来标准化生成错误响应。但你的Security配置中只放行/api/user/**路径,/error端点不在允许匿名访问的列表里,因此被Spring Security拦截,返回403禁止访问。

从你提供的登录失败日志也能验证这一点:认证失败后系统尝试跳转到/error,但该请求被Security拦截,触发Http403ForbiddenEntryPoint返回403。

修复方案

方案1:放行/error端点(最简单直接)

修改你的Security配置,在requestMatchers中添加对/error的放行:

http.csrf(AbstractHttpConfigurer::disable)
        .authorizeHttpRequests(req ->
                req.requestMatchers("/api/user/**", "/error") // 添加/error端点放行
                        .permitAll()
                        .anyRequest()
                        .authenticated()
        )
        .sessionManagement(session -> session.sessionCreationPolicy(STATELESS))
        .authenticationProvider(authenticationProvider)
        .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);

这样当系统转发请求到/error时,不会被Security拦截,Spring Boot就能正常处理你的ResponseStatusException,返回设置的404状态码和错误信息。

方案2:自定义异常处理(更灵活可控)

如果不想放行/error端点,可以配置Spring Security的异常处理器,同时添加全局业务异常处理器,绕过内置错误端点:

第一步:修改Security配置,添加异常处理逻辑

http.csrf(AbstractHttpConfigurer::disable)
        .authorizeHttpRequests(req ->
                req.requestMatchers("/api/user/**")
                        .permitAll()
                        .anyRequest()
                        .authenticated()
        )
        .sessionManagement(session -> session.sessionCreationPolicy(STATELESS))
        .authenticationProvider(authenticationProvider)
        .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
        // 配置Security异常处理
        .exceptionHandling(ex -> 
                ex.authenticationEntryPoint((request, response, authException) -> {
                    // 处理认证失败(如登录密码错误)
                    response.setStatus(HttpStatus.UNAUTHORIZED.value());
                    response.setContentType("application/json");
                    response.getWriter().write("{\"message\":\"Authentication failed: " + authException.getMessage() + "\"}");
                })
                .accessDeniedHandler((request, response, accessDeniedException) -> {
                    // 处理无权限访问
                    response.setStatus(HttpStatus.FORBIDDEN.value());
                    response.setContentType("application/json");
                    response.getWriter().write("{\"message\":\"Access denied: " + accessDeniedException.getMessage() + "\"}");
                })
        );

第二步:添加全局业务异常处理器

创建@RestControllerAdvice类,直接捕获并返回业务异常:

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ResponseStatusException.class)
    public ResponseEntity<Map<String, String>> handleResponseStatusException(ResponseStatusException ex) {
        Map<String, String> response = new HashMap<>();
        response.put("message", ex.getReason());
        return ResponseEntity.status(ex.getStatusCode()).body(response);
    }
}

这种方式可以分别处理Security层面的异常和业务层面的异常,完全绕过内置的/error端点,响应格式更可控。

验证方法

修改后测试业务接口:当触发ResponseStatusException(HttpStatus.NOT_FOUND)时,应该能直接返回404状态码和"Task not found."的响应内容,不再被拦截为403。

内容的提问来源于stack exchange,提问作者Prabod Ashan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 08:42:40