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

Spring Security请求到Dispatcher Servlet前返回403错误求助

问题分析与解决方案

你的核心问题是:请求在到达Dispatcher Servlet前或后续处理中出现的非授权类错误(如参数校验失败、404)被错误返回为403 Forbidden,而非对应HTTP状态码。结合你的代码,问题根源集中在自定义JWT过滤器的异常处理逻辑和Spring Security与Spring MVC的异常流转机制上,以下是具体修复方案:


解决方案1:修复自定义JWT过滤器的异常处理逻辑

当前过滤器仅捕获ExpiredJwtException,且在缺少Authorization头时直接返回403,这会拦截正常的业务异常流转。修改后的过滤器应:

  • 放行无Authorization头的请求,由Spring Security默认逻辑返回401(未认证)而非403
  • 捕获所有JWT相关异常,统一返回401
  • 放行非JWT类异常,交由Spring MVC处理
@Slf4j
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    public static final String BEARER_SUBSTRING = "Bearer ";
    private final UserDetailsService userDetailsService;
    private final JwtService jwtService;
    private final AuthenticationEntryPoint entryPoint = new BearerTokenAuthenticationEntryPoint();

    @Override
    protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws ServletException, IOException {
        final String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
        final String jwt;
        final String userEmail;

        // 无Authorization头时,放行到后续过滤器,由Spring Security处理认证要求
        if (authHeader == null || !authHeader.startsWith(BEARER_SUBSTRING)) {
            filterChain.doFilter(request, response);
            return;
        }

        jwt = authHeader.substring(BEARER_SUBSTRING.length());
        try {
            userEmail = jwtService.extractEmail(jwt);
            if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                UserDetails userDetails = this.userDetailsService.loadUserByUsername(userEmail);
                if (jwtService.isTokenValid(jwt, userDetails)) {
                    UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
                            userDetails, null, userDetails.getAuthorities()
                    );
                    authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    SecurityContextHolder.getContext().setAuthentication(authToken);
                } else {
                    // JWT无效,抛出认证异常返回401
                    entryPoint.commence(request, response, new BadCredentialsException("Invalid JWT token"));
                    return;
                }
            }
            // 放行请求到Dispatcher Servlet
            filterChain.doFilter(request, response);
        } catch (ExpiredJwtException e) {
            log.error("JWT token expired: {}", e.getMessage());
            entryPoint.commence(request, response, new CredentialsExpiredException("JWT token expired"));
        } catch (JwtException e) {
            log.error("JWT processing failed: {}", e.getMessage());
            entryPoint.commence(request, response, new BadCredentialsException("Invalid JWT token"));
        } catch (Exception e) {
            log.error("Unexpected error during authentication: {}", e.getMessage());
            // 放行非JWT异常,交由Spring MVC处理
            filterChain.doFilter(request, response);
        }
    }
}

解决方案2:配置Spring Security异常处理规则

在SecurityFilterChain中明确区分认证失败(401)和授权失败(403)的处理逻辑,避免异常混淆:

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http, UserDetailsService userDetailsService, JwtService jwtService) throws Exception {
    http
            .csrf().disable()
            .cors().and()
            .authorizeHttpRequests(c -> c
                    .requestMatchers("/api/v1/employee/**").hasAnyRole(ADMIN.name(), USER.name(), HR.name())
                    .requestMatchers(GET, basePath + "/employee/**").hasAnyAuthority(ADMIN_READ.name(), USER_READ.name())
                    .requestMatchers(POST, basePath + "/employee/**").hasAnyAuthority(ADMIN_CREATE.name(), USER_CREATE.name())
                    .requestMatchers(PUT, basePath + "/employee/**").hasAnyAuthority(ADMIN_UPDATE.name(), USER_UPDATE.name())
                    .requestMatchers(DELETE, basePath + "/employee/**").hasAnyAuthority(ADMIN_DELETE.name(), USER_DELETE.name())
                    .anyRequest().authenticated())
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
            .authenticationProvider(authenticationProvider)
            .addFilterBefore(new JwtAuthenticationFilter(userDetailsService, jwtService), UsernamePasswordAuthenticationFilter.class)
            .logout(logout -> logout
                    .logoutUrl(basePath + "/auth/logout")
                    .addLogoutHandler(logoutHandler)
                    .logoutSuccessHandler((request, response, authentication) -> SecurityContextHolder.clearContext()))
            // 配置异常处理
            .exceptionHandling(exception -> exception
                    .authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint()) // 认证失败返回401
                    .accessDeniedHandler((request, response, ex) -> { // 授权失败返回403
                        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
                        response.getWriter().write("Access Denied: " + ex.getMessage());
                    }));

    return http.build();
}

解决方案3:配置全局异常处理器处理Spring MVC异常

创建全局异常处理器,专门处理参数校验、404等业务/HTTP异常,返回正确状态码:

@RestControllerAdvice
public class GlobalExceptionHandler {

    // 处理参数校验异常
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getAllErrors().forEach(error -> {
            String fieldName = ((FieldError) error).getField();
            String errorMessage = error.getDefaultMessage();
            errors.put(fieldName, errorMessage);
        });
        return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
    }

    // 处理404异常
    @ExceptionHandler(NoHandlerFoundException.class)
    public ResponseEntity<String> handleNotFound(NoHandlerFoundException ex) {
        return new ResponseEntity<>("Resource not found: " + ex.getRequestURL(), HttpStatus.NOT_FOUND);
    }

    // 处理通用服务器异常
    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleGeneralException(Exception ex) {
        log.error("Unexpected error: {}", ex.getMessage());
        return new ResponseEntity<>("Internal server error", HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

同时在application.properties中开启404异常抛出:

spring.mvc.throw-exception-if-no-handler-found=true
spring.web.resources.add-mappings=false

验证步骤

  1. 不带Authorization头访问/api/v1/offer,应返回401而非403
  2. 带有效JWT访问不存在的URL,应返回404
  3. 带有效JWT访问/api/v1/offer并提交错误参数,应返回400
  4. 带过期JWT访问,应返回401

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 12:54:55