Spring Security配置JWT后访问不存在端点返回403而非404问题
问题:携带有效JWT访问不存在端点返回403而非404
已基于Spring Security配置自定义JWT认证过滤器,正常请求可正常工作,但携带有效JWT令牌访问不存在的随机端点时,系统返回403状态码,预期应为404。
相关配置代码:
@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf() .disable() .authorizeHttpRequests() .anyRequest() .authenticated() .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authenticationProvider(authenticationProvider()) .addFilterBefore(new JwtAuthFilter(jwtManager, customerCredentialsDao), UsernamePasswordAuthenticationFilter.class); return http.build(); } @Bean CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(Collections.singletonList("*")); configuration.setAllowedMethods(Arrays.asList("GET", "POST", "OPTIONS", "DELETE", "PUT", "PATCH")); configuration.setAllowedHeaders(Arrays.asList("X-Requested-With", "Origin", "Content-Type", "Accept", "Authorization")); configuration.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source; }
过滤器代码:
package catsit.config; import catsit.authentication.CustomerContext; import catsit.authentication.CustomerCredentialsDao; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; import static org.springframework.http.HttpHeaders.AUTHORIZATION; @RequiredArgsConstructor public class JwtAuthFilter extends OncePerRequestFilter { private final JwtManager jwtManager; private final CustomerCredentialsDao customerCredentialsDao; @Override protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { final String authorizationHeader = request.getHeader(AUTHORIZATION); if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer")) { filterChain.doFilter(request, response); return; } String jwt = authorizationHeader.substring(7); if (jwtManager.isTokenValid(jwt)) { CustomerContext userDetails = customerCredentialsDao.findCredentialsAndIdByEmail(jwtManager.extractEmail(jwt)); UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities() ); authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authenticationToken); } filterChain.doFilter(request, response); } }
原因分析
问题源于Spring Security的执行顺序和配置逻辑:
- Spring Security过滤器链优先于DispatcherServlet执行,所有请求(包括不存在的端点)都会先经过认证检查。
- 配置中的
anyRequest().authenticated()要求所有请求必须认证,携带有效JWT时,JwtAuthFilter会将认证信息存入SecurityContext,Security判定请求已认证通过。 - 后续DispatcherServlet发现端点不存在,本应返回404,但Spring Security的ExceptionTranslationFilter会介入处理
NoHandlerFoundException,结合"已认证但无对应权限"的判定,最终返回403。
简言之:Security先通过了认证检查,但后续找不到端点的异常被Security转换成了403,而非原生的404。
解决方案
调整Security配置,仅对存在控制器映射的端点要求认证,不存在的端点直接放行,让DispatcherServlet返回原生404:
- 注入
HandlerMappingIntrospector,它可以判断请求是否对应已配置的控制器端点。 - 修改
authorizeHttpRequests规则,只对有映射的请求做认证要求,其他请求直接放行。
修改后的SecurityFilterChain配置代码:
@Bean public SecurityFilterChain filterChain(HttpSecurity http, HandlerMappingIntrospector introspector) throws Exception { http .csrf().disable() .authorizeHttpRequests(auth -> auth // 仅对存在控制器映射的请求要求认证 .requestMatchers(req -> { try { return introspector.hasMapping(req); } catch (Exception e) { return false; } }).authenticated() // 不存在的端点直接放行 .anyRequest().permitAll() ) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authenticationProvider(authenticationProvider()) .addFilterBefore(new JwtAuthFilter(jwtManager, customerCredentialsDao), UsernamePasswordAuthenticationFilter.class); return http.build(); }
配置生效后:
- 存在的端点会正常经过JWT认证,未携带有效令牌返回401,认证通过则正常处理业务。
- 不存在的端点会被Security直接放行,DispatcherServlet找不到对应控制器,返回原生404。
内容的提问来源于stack exchange,提问作者dev-rifaii
相关产品推荐
相关产品推荐

