Spring Boot 3.0触发异常时返回403无响应体问题求助
问题解决:Spring Boot 3.0 异常返回403空响应
问题根源
Spring Boot 3.0 配套的Spring Security 6调整了异常处理逻辑,默认情况下,请求经过Security过滤器链时,未被Security处理的异常会被拦截并返回默认的403空响应,不会流转到Spring MVC的全局异常处理器,这就是新版本异常返回行为变化的原因。
解决步骤
1. 调整Security配置,添加异常处理配置
修改SecurityConfiguration中的filterChain方法,加入exceptionHandling()配置,接管授权/认证异常的返回格式,同时让其他异常继续流转到MVC处理器:
return http .cors().and() .csrf((csrf) -> csrf.disable()) .sessionManagement((session) -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS) ) .authorizeHttpRequests((authorize) -> authorize .requestMatchers("/login/**", "/trackers/camera/**").permitAll() .requestMatchers("/sites/**").hasAnyRole(Role.OWNER.name()) .anyRequest().authenticated() ) // 新增异常处理配置 .exceptionHandling(exception -> exception // 处理授权异常,返回JSON格式 .accessDeniedHandler((request, response, ex) -> { response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setStatus(HttpServletResponse.SC_FORBIDDEN); Map<String, Object> body = new HashMap<>(); body.put("status", HttpServletResponse.SC_FORBIDDEN); body.put("error", "Forbidden"); body.put("message", ex.getMessage()); body.put("path", request.getServletPath()); new ObjectMapper().writeValue(response.getOutputStream(), body); }) // 处理认证异常,返回JSON格式 .authenticationEntryPoint((request, response, ex) -> { response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); Map<String, Object> body = new HashMap<>(); body.put("status", HttpServletResponse.SC_UNAUTHORIZED); body.put("error", "Unauthorized"); body.put("message", ex.getMessage()); body.put("path", request.getServletPath()); new ObjectMapper().writeValue(response.getOutputStream(), body); }) ) .addFilter(authenticationFilter) .addFilterBefore(authorizationFilter, UsernamePasswordAuthenticationFilter.class) .build();
2. 配置全局异常处理器,处理参数校验等异常
创建全局异常处理器,捕获MethodArgumentNotValidException这类非Security相关的异常,返回标准化JSON:
@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<Map<String, Object>> handleValidationExceptions(MethodArgumentNotValidException ex) { Map<String, Object> body = new HashMap<>(); body.put("status", HttpServletResponse.SC_BAD_REQUEST); body.put("error", "Bad Request"); // 收集字段校验错误详情 Map<String, String> fieldErrors = new HashMap<>(); ex.getBindingResult().getAllErrors().forEach(error -> { String fieldName = ((FieldError) error).getField(); String errorMessage = error.getDefaultMessage(); fieldErrors.put(fieldName, errorMessage); }); body.put("message", "参数校验失败"); body.put("errors", fieldErrors); body.put("path", ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getServletPath()); return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST); } // 通用异常处理 @ExceptionHandler(Exception.class) public ResponseEntity<Map<String, Object>> handleGeneralExceptions(Exception ex) { Map<String, Object> body = new HashMap<>(); body.put("status", HttpServletResponse.SC_INTERNAL_SERVER_ERROR); body.put("error", "Internal Server Error"); body.put("message", ex.getMessage()); body.put("path", ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getServletPath()); return new ResponseEntity<>(body, HttpStatus.INTERNAL_SERVER_ERROR); } }
3. 修复自定义过滤器的异常传递
确保AuthorizationFilter中出现的异常(如JWT验证失败)不会被吞掉,而是抛出给Security异常处理器处理:
// 在AuthorizationFilter的Bearer认证逻辑中添加异常处理 try { var message = verifier.verify(token); var subject = message.getSubject(); var roles = message.getClaim("roles").asArray(String.class); var authorities = new ArrayList<SimpleGrantedAuthority>(); Arrays.stream(roles).forEach(role -> authorities.add(new SimpleGrantedAuthority(role))); var authenticationToken = new UsernamePasswordAuthenticationToken(subject, token, authorities); SecurityContextHolder.getContext().setAuthentication(authenticationToken); } catch (JWTVerificationException e) { throw new AuthenticationServiceException("无效的JWT令牌", e); }
原理说明
通过Security的accessDeniedHandler和authenticationEntryPoint接管授权/认证异常的返回格式,确保返回JSON;同时全局异常处理器负责处理参数校验、业务逻辑等非Security异常,两者配合就能恢复到旧版本的异常返回行为。
内容的提问来源于stack exchange,提问作者pengemizt
相关产品推荐
相关产品推荐

