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

Spring Boot中Spring Security过滤器重复初始化问题求助

解决Spring Boot中Spring Security过滤器重复注册导致的无保护资源认证异常

我之前把Spring应用迁移到Spring Boot时也碰到过一模一样的问题,你的判断完全正确:Spring Boot默认会自动将所有实现Filter接口的Bean注册到Servlet容器的全局ApplicationFilterChain中,而Spring Security的过滤器(比如FilterSecurityInterceptor、BasicAuthenticationFilter这些XML隐式生成的)本来应该只在Spring Security自己的FilterChainProxy(也就是springSecurityFilterChain)里执行。当无保护的CSS/JS请求经过这些被重复注册的过滤器时,SecurityContext里没有认证信息,就会抛出AuthenticationCredentialsNotFoundException。

下面给你几个可行的解决方案,从临时修复到长期优化都有:

方法1:批量禁用Spring Security过滤器的自动注册

我们可以创建一个配置类,遍历所有Spring Security相关的Filter,禁用它们的Servlet容器自动注册,只保留FilterChainProxy作为Spring Security的入口:

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.web.FilterChainProxy;
import javax.servlet.Filter;
import java.util.List;

@Configuration
public class SecurityFilterRegistrationConfig {

    @Bean
    public FilterRegistrationBean disableSpringSecurityFilterRegistration(List<Filter> filters) {
        filters.stream()
               // 保留FilterChainProxy,它是Spring Security的核心入口
               .filter(filter -> !(filter instanceof FilterChainProxy))
               // 匹配所有Spring Security包下的过滤器
               .filter(filter -> filter.getClass().getPackage().getName().startsWith("org.springframework.security.web"))
               .forEach(filter -> {
                   FilterRegistrationBean<?> registration = new FilterRegistrationBean<>(filter);
                   registration.setEnabled(false);
               });
        return new FilterRegistrationBean<>();
    }
}

这个配置会自动识别所有Spring Security的Filter,阻止它们被注册到Servlet容器的全局链,确保只有FilterChainProxy处理安全请求,你的security="none"配置就能正常生效了。

方法2:迁移到Spring Security Java配置(推荐长期方案)

Spring Boot更推荐用Java配置来管理Spring Security,这样能完全控制过滤器链的创建,从根源上避免隐式Bean导致的重复注册问题。对应你XML配置的Java实现如下:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationEntryPoint;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    // 处理CSS/JS等无保护资源
    @Bean
    public SecurityFilterChain staticResourcesSecurityFilterChain(HttpSecurity http) throws Exception {
        http
               .securityMatcher("/**/*.css", "/**/*.js")
               .authorizeHttpRequests(auth -> auth.anyRequest().permitAll());
        return http.build();
    }

    // Basic认证过滤器链
    @Bean
    public SecurityFilterChain basicAuthSecurityFilterChain(HttpSecurity http) throws Exception {
        http
               .securityMatcher(request -> {
                   String query = request.getQueryString();
                   return query != null && query.contains("basic-auth=on");
               })
               .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.NEVER))
               .authorizeHttpRequests(auth -> auth.anyRequest().fullyAuthenticated())
               .httpBasic();
        return http.build();
    }

    // 表单登录过滤器链
    @Bean
    public SecurityFilterChain formAuthSecurityFilterChain(HttpSecurity http,
                                                          AccessDeniedHandler accessDeniedHandler,
                                                          AuthenticationEntryPoint formLoginEntryPoint) throws Exception {
        http
               .securityMatcher(request -> {
                   String path = request.getServletPath();
                   String query = request.getQueryString();
                   return (query != null && query.contains("form-auth=on"))
                           || "/servlet/login.html".equals(path)
                           || "/servlet/!login.html".equals(path);
               })
               .exceptionHandling(ex -> ex.authenticationEntryPoint(formLoginEntryPoint))
               .authorizeHttpRequests(auth -> auth
                       .requestMatchers("/servlet/login.html", "/servlet/!login.html").anonymous()
                       .anyRequest().fullyAuthenticated())
               .formLogin(form -> form
                       .loginPage("/servlet/login.html")
                       .loginProcessingUrl("/servlet/!login.html")
                       .defaultSuccessUrl("/")
                       .failureHandler(accessDeniedHandler));
        return http.build();
    }

    // 这里可以添加Kerberos认证对应的SecurityFilterChain
}

用Java配置的话,Spring Boot会自动把所有SecurityFilterChain整合到FilterChainProxy中,不会把单个Filter注册到全局链,完全符合Spring Boot的设计规范,后续维护也更清晰。

方法3:检查XML配置中的显式Filter Bean

如果你暂时不想改Java配置,可以检查你的Spring Security XML文件,看看有没有把Spring Security的Filter(比如FilterSecurityInterceptor)显式注册为<bean>。如果有,要么移除这些显式Bean定义,要么为每个这样的Bean创建对应的FilterRegistrationBean并设置enabled=false。

比如:

<bean id="filterSecurityInterceptor" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
    <!-- 你的配置 -->
</bean>

<bean id="disableFilterSecurityInterceptor" class="org.springframework.boot.web.servlet.FilterRegistrationBean">
    <property name="filter" ref="filterSecurityInterceptor"/>
    <property name="enabled" value="false"/>
</bean>

不过这种方法需要逐个处理,不如方法1高效,适合临时应急。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 03:53:13