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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 00:35:30