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
验证步骤
- 不带Authorization头访问
/api/v1/offer,应返回401而非403 - 带有效JWT访问不存在的URL,应返回404
- 带有效JWT访问
/api/v1/offer并提交错误参数,应返回400 - 带过期JWT访问,应返回401
内容的提问来源于stack exchange,提问作者KripDo
相关产品推荐
相关产品推荐

