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

Spring Boot仅指定端点走安全过滤器的配置问题及403排查

问题:Spring POST请求403 Forbidden错误,安全配置不符合需求

我是Spring新手,实现POST方法时遇到403 Forbidden错误,推测是安全过滤器导致的。已经通过视频实现JWT认证且功能正常,但当前安全链会对所有API端点启用安全过滤。我仅希望前缀为api/v1/auth的端点经过安全过滤器(这类端点仅允许已认证用户访问),其他所有端点(包括OAuth2相关、POST请求的评论接口)都允许未认证访问。

所有API都以api/v1为前缀,其他接口都是GetMapping,这是首次遇到403错误。


当前Security配置

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig{
    private final JwtAuthenticationFilter jwtAuthFilter;
    private final AuthenticationProvider authenticationProvider;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .cors()
                .and()
                .csrf()
                .disable()
                .authorizeHttpRequests()
                .requestMatchers("/api/v1/**")
                .permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .oauth2Client(withDefaults())
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authenticationProvider(authenticationProvider)
                .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }
}

JWT过滤器代码

@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    private final JwtService jwtService;
    private final UserDetailsService userDetailsService;
    @Override
    protected void doFilterInternal(
            @NonNull HttpServletRequest request,
            @NonNull HttpServletResponse response,
            @NonNull FilterChain filterChain
    ) throws ServletException, IOException {
        final String authHeader = request.getHeader("Authorization");
        final String jwt;
        final String username;
        if(authHeader == null || authHeader.startsWith("Bearer ")){
            filterChain.doFilter(request,response);
            return;
        }
        jwt = authHeader.substring(7);
        username = jwtService.extractUsername(jwt);
        if(username != null && SecurityContextHolder.getContext().getAuthentication() == null){
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
            if(jwtService.isTokenValid(jwt,userDetails)){
                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities()
                );
                authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            }
        }
        filterChain.doFilter(request,response);
    }
}

出错的Controller代码

@CrossOrigin(origins = "http://localhost:3000/")
@RequestMapping("/api/v1")
@RestController
@AllArgsConstructor
public class ReviewsController {
    private final ReviewsService reviewsService;
    @PostMapping("/games/reviews")
    public ResponseEntity<Reviews> addGameReviewById(@RequestBody Reviews reviews){
        return new ResponseEntity<>(reviewsService.addGameReviewById(reviews),HttpStatus.CREATED);
    }

    @PostMapping("/tech/reviews")
    public ResponseEntity<Reviews> addTechReview(@RequestBody Reviews reviews){
        return new ResponseEntity<>(reviewsService.addTechReview(reviews),HttpStatus.CREATED);
    }
}

Postman请求截图

Postman POST请求截图


解决方案

1. 修正Security配置规则

当前配置的规则逻辑错误:requestMatchers("/api/v1/**").permitAll()后又设置anyRequest().authenticated(),会导致非/api/v1/**的请求需要认证,完全不符合你"仅/api/v1/auth/**需要认证"的需求。修改后的配置如下:

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
            .cors()
            .and()
            .csrf()
            .disable()
            .authorizeHttpRequests(auth -> auth
                    // 允许所有非/auth的api/v1端点、OAuth2相关端点匿名访问
                    .requestMatchers("/api/v1/**", "/oauth2/**")
                    .permitAll()
                    // 仅/api/v1/auth/**路径要求已认证
                    .requestMatchers("/api/v1/auth/**")
                    .authenticated()
                    // 兜底规则:其他请求全部允许
                    .anyRequest()
                    .permitAll()
            )
            .oauth2Client(withDefaults())
            .sessionManagement(session -> session
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )
            .authenticationProvider(authenticationProvider)
            .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
    return http.build();
}

2. 修复JWT过滤器的核心逻辑错误

过滤器中存在条件写反的严重问题:if(authHeader == null || authHeader.startsWith("Bearer "))会导致只要请求头带Bearer前缀就直接跳过认证,完全失效了JWT认证功能,这是403错误的主要诱因。修正后的过滤器代码:

@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    private final JwtService jwtService;
    private final UserDetailsService userDetailsService;
    @Override
    protected void doFilterInternal(
            @NonNull HttpServletRequest request,
            @NonNull HttpServletResponse response,
            @NonNull FilterChain filterChain
    ) throws ServletException, IOException {
        final String authHeader = request.getHeader("Authorization");
        final String jwt;
        final String username;
        // 修正条件:空请求头或不带Bearer前缀时才跳过认证
        if(authHeader == null || !authHeader.startsWith("Bearer ")){
            filterChain.doFilter(request,response);
            return;
        }
        jwt = authHeader.substring(7);
        username = jwtService.extractUsername(jwt);
        if(username != null && SecurityContextHolder.getContext().getAuthentication() == null){
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
            if(jwtService.isTokenValid(jwt,userDetails)){
                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities()
                );
                authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            }
        }
        filterChain.doFilter(request,response);
    }
}

3. 全局配置CORS避免冲突

虽然Controller加了@CrossOrigin,但建议在Security中配置全局CORS规则,避免局部配置引发的跨域问题:

@Bean
public CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration configuration = new CorsConfiguration();
    configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000"));
    configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
    configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type"));
    configuration.setAllowCredentials(true);

    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", configuration);
    return source;
}

然后在Security配置中替换.cors()为.cors(cors -> cors.configurationSource(corsConfigurationSource()))。

4. 验证步骤

  • 测试/api/v1/games/reviews等POST接口:无需携带JWT,应返回201 Created
  • 测试/api/v1/auth/**端点:不带JWT返回403,携带有效JWT返回正常响应
  • 测试OAuth2相关端点:确保能正常匿名访问

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 03:25:01