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

SpringBoot3+SpringSecurity6配置端点安全状态遇403问题求助

问题描述

折腾好几天解决Spring Security配置问题:

  • 编译时遇到多种错误,替换弃用的.antMatchers为.requestMatchers后,又出现“Spring Security无法判断模式是否为MVC”的异常
  • 参考方案消除编译错误后,所有端点仍被完全保护,无法指定非安全端点,启动服务器后访问任意端点均返回403

当前代码

SecurityConfig

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {


    private final JwtAuthFilter jwtAuthFilter;
    private final AuthenticationProvider authenticationProvider;

    @Bean
    MvcRequestMatcher.Builder mvc(HandlerMappingIntrospector introspector) {
        return new MvcRequestMatcher.Builder(introspector)
                .servletPath("/api/v1");
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http, MvcRequestMatcher.Builder mvc) throws Exception {
        http
            .csrf(csrf -> csrf.disable())
            .authorizeHttpRequests((auth) -> auth
                .requestMatchers(mvc.pattern("/auth/**")).permitAll()
                .anyRequest().authenticated()
            )
            // 无状态认证
            .sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authenticationProvider(authenticationProvider)
            .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }
}

JwtAuthFilter

@Component
@RequiredArgsConstructor
public class JwtAuthFilter extends OncePerRequestFilter {

    private final Middleware jwtService;

    private final UserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(@NonNull HttpServletRequest request, 
                                    @NonNull HttpServletResponse response, 
                                    @NonNull FilterChain filterChain)
            throws ServletException, IOException {

        try {
            final String authHeader = request.getHeader("Authorization");
            final String jwtToken;
            final String userEmail;

            if (authHeader == null || !authHeader.startsWith("Bearer ")) {
                filterChain.doFilter(request, response);
                return;
            }

            jwtToken = authHeader.substring(7);
            userEmail = jwtService.extractUsername(jwtToken);

            if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                UserDetails userDetails = this.userDetailsService.loadUserByUsername(userEmail);

                if (jwtService.isTokenValid(jwtToken, userDetails)) {
                    UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities()
                    );
                    authToken.setDetails(
                        new WebAuthenticationDetailsSource().buildDetails(request)
                    );
                    SecurityContextHolder.getContext().setAuthentication(authToken);
                }
            }

            filterChain.doFilter(request, response);

        } catch (Exception ex) {
            // 此处处理异常
        }

        // 继续过滤器链
        filterChain.doFilter(request, response);
    }
}

问题排查与解决

1. MvcRequestMatcher路径匹配错误

你在MvcRequestMatcher.Builder中设置了servletPath("/api/v1"),这意味着mvc.pattern("/auth/**")实际匹配的是/api/v1/auth/**路径。如果你的实际接口路径是/auth/**(不带/api/v1前缀),那么该匹配规则不会生效,导致/auth端点仍被拦截。

解决方式:

  • 如果接口确实带有/api/v1前缀,请求时需使用/api/v1/auth/xxx路径
  • 如果接口不带该前缀,删除.servletPath("/api/v1")配置,或调整pattern为"/api/v1/auth/**"以匹配实际路径

2. JwtAuthFilter重复调用过滤器链

doFilterInternal方法中存在致命错误:try块内已调用一次filterChain.doFilter(request, response),方法末尾又重复调用了一次。这会导致请求被处理两次,无token的请求(如/auth端点)即使通过了permitAll授权,第二次调用时也会被重新拦截,最终返回403。

解决方式:删除方法末尾的filterChain.doFilter(request, response);,仅保留try块内的一次调用。

3. 临时验证权限配置

若不确定是否为MvcRequestMatcher的问题,可以临时改用AntPathRequestMatcher验证:

.requestMatchers(new AntPathRequestMatcher("/auth/**")).permitAll()

如果此配置能正常访问/auth端点,再回头调整MvcRequestMatcher的配置。


修正后的代码示例

修正后的SecurityConfig

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final JwtAuthFilter jwtAuthFilter;
    private final AuthenticationProvider authenticationProvider;

    @Bean
    MvcRequestMatcher.Builder mvc(HandlerMappingIntrospector introspector) {
        // 接口不带/api/v1前缀时,删除servletPath配置
        return new MvcRequestMatcher.Builder(introspector);
        // 接口带前缀时,保留以下配置,请求路径需对应/api/v1/auth/**
        // return new MvcRequestMatcher.Builder(introspector).servletPath("/api/v1");
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http, MvcRequestMatcher.Builder mvc) throws Exception {
        http
            .csrf(csrf -> csrf.disable())
            .authorizeHttpRequests(auth -> auth
                .requestMatchers(mvc.pattern("/auth/**")).permitAll()
                .anyRequest().authenticated()
            )
            .sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authenticationProvider(authenticationProvider)
            .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }
}

修正后的JwtAuthFilter

@Component
@RequiredArgsConstructor
public class JwtAuthFilter extends OncePerRequestFilter {

    private final Middleware jwtService;
    private final UserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(@NonNull HttpServletRequest request, 
                                    @NonNull HttpServletResponse response, 
                                    @NonNull FilterChain filterChain)
            throws ServletException, IOException {

        try {
            final String authHeader = request.getHeader("Authorization");
            final String jwtToken;
            final String userEmail;

            if (authHeader == null || !authHeader.startsWith("Bearer ")) {
                filterChain.doFilter(request, response);
                return;
            }

            jwtToken = authHeader.substring(7);
            userEmail = jwtService.extractUsername(jwtToken);

            if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                UserDetails userDetails = this.userDetailsService.loadUserByUsername(userEmail);

                if (jwtService.isTokenValid(jwtToken, userDetails)) {
                    UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities()
                    );
                    authToken.setDetails(
                        new WebAuthenticationDetailsSource().buildDetails(request)
                    );
                    SecurityContextHolder.getContext().setAuthentication(authToken);
                }
            }

            filterChain.doFilter(request, response);

        } catch (Exception ex) {
            // 可添加日志记录异常信息,例如:logger.error("JWT过滤失败", ex);
        }
        // 已删除重复的过滤器链调用
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 19:45:05