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

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的执行顺序和配置逻辑:

  1. Spring Security过滤器链优先于DispatcherServlet执行,所有请求(包括不存在的端点)都会先经过认证检查。
  2. 配置中的anyRequest().authenticated()要求所有请求必须认证,携带有效JWT时,JwtAuthFilter会将认证信息存入SecurityContext,Security判定请求已认证通过。
  3. 后续DispatcherServlet发现端点不存在,本应返回404,但Spring Security的ExceptionTranslationFilter会介入处理NoHandlerFoundException,结合"已认证但无对应权限"的判定,最终返回403。

简言之:Security先通过了认证检查,但后续找不到端点的异常被Security转换成了403,而非原生的404。


解决方案

调整Security配置,仅对存在控制器映射的端点要求认证,不存在的端点直接放行,让DispatcherServlet返回原生404:

  1. 注入HandlerMappingIntrospector,它可以判断请求是否对应已配置的控制器端点。
  2. 修改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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 06:35:27