Spring Boot仅指定端点走安全过滤器的配置问题及403排查
我是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请求截图

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

