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

