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

升级Spring Security至6.0.5+遇Servlet规范相关异常求助

问题描述

在Tomcat 10.1.7上使用Spring 6、Java 17构建CAS认证的Web应用,以下版本组合运行正常:

  • spring-security-config: 6.0.4
  • spring-webmvc: 6.0.11
  • spring-security-cas: 6.1.2
  • spring-session-jdbc: 3.1.1

升级spring-security-config到6.0.5及以上版本后,部署时抛出异常:

java.lang.UnsupportedOperationException: Section 4.4 of the Servlet 3.0 specification does not permit this method to be called from a ServletContextListener that was not defined in web.xml, a web-fragment.xml file nor annotated with @WebListener

异常触发于SecurityConfiguration中requestMatchers方法的首次调用,原因是Spring Security 6.0.5+在AbstractRequestMatcherRegistry.requestMatchers中新增了对jakarta.servlet.ServletContext.getServletRegistrations()的调用,当前应用的初始化方式导致该调用违反Servlet 3.0规范。

已尝试添加空的@WebListener ServletContextListener、移除AbstractSecurityWebApplicationInitializer,均未解决问题。


解决方案1:显式禁用ServletRegistrationLookup

Spring Security 6.0.5+支持通过配置禁用对Servlet注册信息的查询,直接规避违规调用。在SecurityFilterChain配置中添加如下设置:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http, UserDetailsService userDetailsService) throws Exception {
    http
        .authorizeHttpRequests(auth -> auth
            .lookupServletRegistration(false) // 禁用Servlet注册查询
            .requestMatchers("/index*", "/search*", "/show*").hasAuthority(Permission.QUERY.getName())
            .requestMatchers("/edit*").hasAuthority(Permission.MANAGING.getName())
            .anyRequest().permitAll()
        )
        // 原有异常处理、过滤器、logout等配置保持不变
        .exceptionHandling(excep -> excep
            .accessDeniedPage("/notAuthorized")
        )
        .addFilter(casAuthenticationFilter(userDetailsService))
        .addFilterAfter(new CsrfCookieGeneratorFilter(), CsrfFilter.class)
        .addFilterBefore(casSingleLogoutFilter(), CasAuthenticationFilter.class)
        .httpBasic(httpBasic -> httpBasic
            .authenticationEntryPoint(casAuthenticationEntryPoint())
        )
        .logout(logout -> logout
            .logoutRequestMatcher(new AntPathRequestMatcher("/close-sesion"))
            .logoutSuccessUrl(env.getProperty("security.cas.logoutUrl"))
            .deleteCookies("auth_code","JSESSIONID","CSRF-TOKEN")
            .invalidateHttpSession(true).permitAll()
        )
        .headers(headers -> headers
            .xssProtection(xss -> xss.headerValue(XXssProtectionHeaderWriter.HeaderValue.ENABLED_MODE_BLOCK))
            .contentSecurityPolicy(csp -> csp.policyDirectives("script-src 'self'; form-action 'self';"))
        );
    
    return http.build();
}

解决方案2:调整应用初始化顺序

当前通过AbstractAnnotationConfigDispatcherServletInitializer同时加载Web和Security配置,导致Security配置在ServletContext完全初始化前被处理。拆分配置加载时机,让Security在ServletContext初始化后期加载:

步骤1:拆分Security配置类

创建独立的根安全配置类,仅包含Spring Security相关配置:

@Configuration
@ComponentScan(basePackages = "com.myapp.security")
@EnableWebSecurity
@PropertySource(value = { "classpath:dga-security.properties" }, ignoreResourceNotFound = false)
public class RootSecurityConfig {
    @Autowired
    private Environment env;
    
    // 原SecurityConfiguration中的所有Bean定义(filterChain、casAuthenticationFilter等)移到这里
}

步骤2:修改DispatcherServlet初始化类

让AnnotationConfigDispatcherServletInitializer仅加载Web配置:

public class AnnotationConfigDispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[] {WebConfig.class}; // 仅加载Web配置
    }
  
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return null;
    }
  
    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }
    
    @Override
    protected Filter[] getServletFilters() {
        return null;
    }
    
    @Override
    protected void customizeRegistration(Dynamic registration) {
        Properties prop = getProperties("mensajes.properties");
        
        File uploadDirectory = new File(System.getProperty("java.io.tmpdir"));
        long maxUploadSize = 10485760;
        registration.setMultipartConfig(new MultipartConfigElement(
                uploadDirectory.getAbsolutePath(),
                maxUploadSize,
                maxUploadSize*2,
                (int)(maxUploadSize/2)
        ));
    }
}

步骤3:修改Security初始化类

让SecurityWebApplicationInitializer显式指定安全配置类,确保在ServletContext合适阶段加载:

public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
    public SecurityWebApplicationInitializer() {
        super(RootSecurityConfig.class); // 显式指定Security配置类
    }

    @Override
    protected void beforeSpringSecurityFilterChain(ServletContext servletContext) {
        insertFilters(servletContext, new OncePerRequestFilter(){
            @Override
            protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
                try {
                    filterChain.doFilter(request, response);
                } catch (MaxUploadSizeExceededException e) {
                    // 处理文件过大异常
                } catch (Throwable th) {
                    throw th;
                }               
            }
        });
        insertFilters(servletContext, new MultipartFilter());
    }
}

解决方案3:升级Servlet规范版本

Tomcat 10.1.7支持Jakarta Servlet 6.0,该版本对getServletRegistrations()的调用限制有所放宽。修改web.xml适配Servlet 6.0规范:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
         version="6.0">
    
    <display-name>myapp</display-name>
    
    <session-config>
        <session-timeout>30</session-timeout>
        <cookie-config>
            <http-only>true</http-only>
            <secure>false</secure>
        </cookie-config>
        <tracking-mode>COOKIE</tracking-mode>
    </session-config>
    
    <error-page>
        <error-code>400</error-code>
        <location>/WEB-INF/jsp/errors/400.jsp</location>
    </error-page>
    <!-- 其他错误页面配置 -->
</web-app>

确保pom.xml中Jakarta Servlet依赖版本已为6.0.0(当前配置已满足)。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 17:48:11