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
相关产品推荐
相关产品推荐

