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

