VYPR
High severityNVD Advisory· Published Jul 18, 2023· Updated Oct 25, 2024

CVE-2023-34035

CVE-2023-34035

Description

Spring Security versions 5.8 prior to 5.8.5, 6.0 prior to 6.0.5, and 6.1 prior to 6.1.2 could be susceptible to authorization rule misconfiguration if the application uses requestMatchers(String) and multiple servlets, one of them being Spring MVC’s DispatcherServlet. (DispatcherServlet is a Spring MVC component that maps HTTP endpoints to methods on @Controller-annotated classes.)

Specifically, an application is vulnerable when all of the following are true:

  • Spring MVC is on the classpath
  • Spring Security is securing more than one servlet in a single application (one of them being Spring MVC’s DispatcherServlet)
  • The application uses requestMatchers(String) to refer to endpoints that are not Spring MVC endpoints

An application is not vulnerable if any of the following is true:

  • The application does not have Spring MVC on the classpath
  • The application secures no servlets other than Spring MVC’s DispatcherServlet
  • The application uses requestMatchers(String) only for Spring MVC endpoints

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
org.springframework.security:spring-security-configMaven
>= 5.8.0, < 5.8.55.8.5
org.springframework.security:spring-security-configMaven
>= 6.0.0, < 6.0.56.0.5
org.springframework.security:spring-security-configMaven
>= 6.1.0, < 6.1.26.1.2

Affected products

1
  • Range: Spring Security 5.8.0 to 5.8.4, Spring Security 6.0.0 to 6.0.4, Spring Security 6.1.0 to 6.1.1

Patches

3
4e3bec904a54

Specify MVC endpoint

1 file changed · +12 3
  • servlet/java-configuration/authentication/preauth/src/main/java/example/SecurityConfiguration.java+12 3 modified
    @@ -1,5 +1,5 @@
     /*
    - * Copyright 2002-2016 the original author or authors.
    + * Copyright 2002-2023 the original author or authors.
      *
      * Licensed under the Apache License, Version 2.0 (the "License");
      * you may not use this file except in compliance with the License.
    @@ -17,31 +17,40 @@
     
     import org.springframework.context.annotation.Bean;
     import org.springframework.context.annotation.Configuration;
    +import org.springframework.context.annotation.Scope;
     import org.springframework.security.config.annotation.web.builders.HttpSecurity;
     import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
     import org.springframework.security.core.userdetails.User;
     import org.springframework.security.core.userdetails.UserDetails;
     import org.springframework.security.core.userdetails.UserDetailsService;
     import org.springframework.security.provisioning.InMemoryUserDetailsManager;
     import org.springframework.security.web.SecurityFilterChain;
    +import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
    +import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
     
     @Configuration
     @EnableWebSecurity
     public class SecurityConfiguration {
     
     	@Bean
    -	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    +	public SecurityFilterChain securityFilterChain(HttpSecurity http, MvcRequestMatcher.Builder mvc) throws Exception {
     		// @formatter:off
     		http
     				.authorizeHttpRequests((authorize) -> authorize
    -						.requestMatchers("/login", "/resources/**").permitAll()
    +						.requestMatchers(mvc.pattern("/login"), mvc.pattern("/resources/**")).permitAll()
     						.anyRequest().authenticated()
     				)
     				.jee((jee) -> jee.mappableRoles("USER", "ADMIN"));
     		// @formatter:on
     		return http.build();
     	}
     
    +	@Scope("prototype")
    +	@Bean
    +	MvcRequestMatcher.Builder mvc(HandlerMappingIntrospector introspector) {
    +		return new MvcRequestMatcher.Builder(introspector);
    +	}
    +
     	// @formatter:off
     	@Bean
     	public UserDetailsService userDetailsService() {
    
bb46a5427005

Add DispatcherServlet to Tests

6 files changed · +150 68
  • config/src/test/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistryTests.java+2 29 modified
    @@ -18,22 +18,18 @@
     
     import java.lang.reflect.Field;
     import java.lang.reflect.Modifier;
    -import java.util.LinkedHashMap;
     import java.util.List;
    -import java.util.Map;
     
     import javax.servlet.DispatcherType;
     import javax.servlet.Servlet;
    -import javax.servlet.ServletContext;
    -import javax.servlet.ServletRegistration;
     
    -import org.jetbrains.annotations.NotNull;
     import org.junit.jupiter.api.BeforeEach;
     import org.junit.jupiter.api.Test;
     
     import org.springframework.beans.factory.NoSuchBeanDefinitionException;
     import org.springframework.context.ApplicationContext;
     import org.springframework.http.HttpMethod;
    +import org.springframework.security.config.MockServletContext;
     import org.springframework.security.config.annotation.ObjectPostProcessor;
     import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
     import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
    @@ -70,10 +66,8 @@ public <O> O postProcess(O object) {
     	public void setUp() {
     		this.matcherRegistry = new TestRequestMatcherRegistry();
     		this.context = mock(WebApplicationContext.class);
    -		ServletContext servletContext = new MockServletContext();
    -		servletContext.addServlet("dispatcherServlet", DispatcherServlet.class);
     		given(this.context.getBean(ObjectPostProcessor.class)).willReturn(NO_OP_OBJECT_POST_PROCESSOR);
    -		given(this.context.getServletContext()).willReturn(servletContext);
    +		given(this.context.getServletContext()).willReturn(MockServletContext.mvc());
     		this.matcherRegistry.setApplicationContext(this.context);
     	}
     
    @@ -256,25 +250,4 @@ protected List<RequestMatcher> chainRequestMatchers(List<RequestMatcher> request
     
     	}
     
    -	private static class MockServletContext extends org.springframework.mock.web.MockServletContext {
    -
    -		private final Map<String, ServletRegistration> registrations = new LinkedHashMap<>();
    -
    -		@NotNull
    -		@Override
    -		public ServletRegistration.Dynamic addServlet(@NotNull String servletName, Class<? extends Servlet> clazz) {
    -			ServletRegistration.Dynamic dynamic = mock(ServletRegistration.Dynamic.class);
    -			given(dynamic.getClassName()).willReturn(clazz.getName());
    -			this.registrations.put(servletName, dynamic);
    -			return dynamic;
    -		}
    -
    -		@NotNull
    -		@Override
    -		public Map<String, ? extends ServletRegistration> getServletRegistrations() {
    -			return this.registrations;
    -		}
    -
    -	}
    -
     }
    
  • config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeRequestsTests.java+2 2 modified
    @@ -29,10 +29,10 @@
     import org.springframework.mock.web.MockFilterChain;
     import org.springframework.mock.web.MockHttpServletRequest;
     import org.springframework.mock.web.MockHttpServletResponse;
    -import org.springframework.mock.web.MockServletContext;
     import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
     import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
     import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    +import org.springframework.security.config.MockServletContext;
     import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
     import org.springframework.security.config.annotation.web.builders.HttpSecurity;
     import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    @@ -75,7 +75,7 @@ public class AuthorizeRequestsTests {
     
     	@BeforeEach
     	public void setup() {
    -		this.servletContext = spy(new MockServletContext());
    +		this.servletContext = spy(MockServletContext.mvc());
     		this.request = new MockHttpServletRequest("GET", "");
     		this.request.setMethod("GET");
     		this.response = new MockHttpServletResponse();
    
  • config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecuritySecurityMatchersTests.java+2 32 modified
    @@ -18,14 +18,9 @@
     
     import java.lang.reflect.Field;
     import java.lang.reflect.Modifier;
    -import java.util.LinkedHashMap;
    -import java.util.Map;
     
    -import javax.servlet.Servlet;
    -import javax.servlet.ServletRegistration;
     import javax.servlet.http.HttpServletResponse;
     
    -import org.jetbrains.annotations.NotNull;
     import org.junit.jupiter.api.AfterEach;
     import org.junit.jupiter.api.BeforeEach;
     import org.junit.jupiter.api.Test;
    @@ -39,6 +34,7 @@
     import org.springframework.mock.web.MockFilterChain;
     import org.springframework.mock.web.MockHttpServletRequest;
     import org.springframework.mock.web.MockHttpServletResponse;
    +import org.springframework.security.config.MockServletContext;
     import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
     import org.springframework.security.config.annotation.web.builders.HttpSecurity;
     import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    @@ -52,15 +48,12 @@
     import org.springframework.web.bind.annotation.RequestMapping;
     import org.springframework.web.bind.annotation.RestController;
     import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
    -import org.springframework.web.servlet.DispatcherServlet;
     import org.springframework.web.servlet.config.annotation.EnableWebMvc;
     import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
     import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
     import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
     
     import static org.assertj.core.api.Assertions.assertThat;
    -import static org.mockito.BDDMockito.given;
    -import static org.mockito.Mockito.mock;
     import static org.springframework.security.config.Customizer.withDefaults;
     
     /**
    @@ -240,9 +233,7 @@ public void securityMatchersWhenMultiMvcMatcherThenAllPathsAreDenied() throws Ex
     	public void loadConfig(Class<?>... configs) {
     		this.context = new AnnotationConfigWebApplicationContext();
     		this.context.register(configs);
    -		MockServletContext servletContext = new MockServletContext();
    -		servletContext.addServlet("dispatcherServlet", DispatcherServlet.class);
    -		this.context.setServletContext(servletContext);
    +		this.context.setServletContext(MockServletContext.mvc());
     		this.context.refresh();
     		this.context.getAutowireCapableBeanFactory().autowireBean(this);
     	}
    @@ -573,25 +564,4 @@ public void configurePathMatch(PathMatchConfigurer configurer) {
     
     	}
     
    -	private static class MockServletContext extends org.springframework.mock.web.MockServletContext {
    -
    -		private final Map<String, ServletRegistration> registrations = new LinkedHashMap<>();
    -
    -		@NotNull
    -		@Override
    -		public ServletRegistration.Dynamic addServlet(@NotNull String servletName, Class<? extends Servlet> clazz) {
    -			ServletRegistration.Dynamic dynamic = mock(ServletRegistration.Dynamic.class);
    -			given(dynamic.getClassName()).willReturn(clazz.getName());
    -			this.registrations.put(servletName, dynamic);
    -			return dynamic;
    -		}
    -
    -		@NotNull
    -		@Override
    -		public Map<String, ? extends ServletRegistration> getServletRegistrations() {
    -			return this.registrations;
    -		}
    -
    -	}
    -
     }
    
  • config/src/test/java/org/springframework/security/config/annotation/web/configurers/UrlAuthorizationConfigurerTests.java+2 2 modified
    @@ -31,8 +31,8 @@
     import org.springframework.mock.web.MockFilterChain;
     import org.springframework.mock.web.MockHttpServletRequest;
     import org.springframework.mock.web.MockHttpServletResponse;
    -import org.springframework.mock.web.MockServletContext;
     import org.springframework.security.config.Customizer;
    +import org.springframework.security.config.MockServletContext;
     import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
     import org.springframework.security.config.annotation.web.builders.HttpSecurity;
     import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    @@ -167,7 +167,7 @@ public void multiMvcMatchersConfig() throws Exception {
     	public void loadConfig(Class<?>... configs) {
     		this.context = new AnnotationConfigWebApplicationContext();
     		this.context.register(configs);
    -		this.context.setServletContext(new MockServletContext());
    +		this.context.setServletContext(MockServletContext.mvc());
     		this.context.refresh();
     		this.context.getAutowireCapableBeanFactory().autowireBean(this);
     	}
    
  • config/src/test/java/org/springframework/security/config/MockServletContext.java+139 0 added
    @@ -0,0 +1,139 @@
    +/*
    + * Copyright 2002-2022 the original author or authors.
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *      https://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +
    +package org.springframework.security.config;
    +
    +import java.util.Collection;
    +import java.util.LinkedHashMap;
    +import java.util.Map;
    +import java.util.Set;
    +
    +import javax.servlet.MultipartConfigElement;
    +import javax.servlet.Servlet;
    +import javax.servlet.ServletRegistration;
    +import javax.servlet.ServletSecurityElement;
    +
    +import org.springframework.lang.NonNull;
    +import org.springframework.web.servlet.DispatcherServlet;
    +
    +public class MockServletContext extends org.springframework.mock.web.MockServletContext {
    +
    +	private final Map<String, ServletRegistration> registrations = new LinkedHashMap<>();
    +
    +	public static MockServletContext mvc() {
    +		MockServletContext servletContext = new MockServletContext();
    +		servletContext.addServlet("dispatcherServlet", DispatcherServlet.class);
    +		return servletContext;
    +	}
    +
    +	@NonNull
    +	@Override
    +	public ServletRegistration.Dynamic addServlet(@NonNull String servletName, Class<? extends Servlet> clazz) {
    +		ServletRegistration.Dynamic dynamic = new MockServletRegistration(servletName, clazz);
    +		this.registrations.put(servletName, dynamic);
    +		return dynamic;
    +	}
    +
    +	@NonNull
    +	@Override
    +	public Map<String, ? extends ServletRegistration> getServletRegistrations() {
    +		return this.registrations;
    +	}
    +
    +	private static class MockServletRegistration implements ServletRegistration.Dynamic {
    +
    +		private final String name;
    +
    +		private final Class<?> clazz;
    +
    +		MockServletRegistration(String name, Class<?> clazz) {
    +			this.name = name;
    +			this.clazz = clazz;
    +		}
    +
    +		@Override
    +		public void setLoadOnStartup(int loadOnStartup) {
    +
    +		}
    +
    +		@Override
    +		public Set<String> setServletSecurity(ServletSecurityElement constraint) {
    +			return null;
    +		}
    +
    +		@Override
    +		public void setMultipartConfig(MultipartConfigElement multipartConfig) {
    +
    +		}
    +
    +		@Override
    +		public void setRunAsRole(String roleName) {
    +
    +		}
    +
    +		@Override
    +		public void setAsyncSupported(boolean isAsyncSupported) {
    +
    +		}
    +
    +		@Override
    +		public Set<String> addMapping(String... urlPatterns) {
    +			return null;
    +		}
    +
    +		@Override
    +		public Collection<String> getMappings() {
    +			return null;
    +		}
    +
    +		@Override
    +		public String getRunAsRole() {
    +			return null;
    +		}
    +
    +		@Override
    +		public String getName() {
    +			return this.name;
    +		}
    +
    +		@Override
    +		public String getClassName() {
    +			return this.clazz.getName();
    +		}
    +
    +		@Override
    +		public boolean setInitParameter(String name, String value) {
    +			return false;
    +		}
    +
    +		@Override
    +		public String getInitParameter(String name) {
    +			return null;
    +		}
    +
    +		@Override
    +		public Set<String> setInitParameters(Map<String, String> initParameters) {
    +			return null;
    +		}
    +
    +		@Override
    +		public Map<String, String> getInitParameters() {
    +			return null;
    +		}
    +
    +	}
    +
    +}
    
  • config/src/test/java/org/springframework/security/config/test/SpringTestContext.java+3 3 modified
    @@ -28,8 +28,8 @@
     
     import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
     import org.springframework.mock.web.MockServletConfig;
    -import org.springframework.mock.web.MockServletContext;
     import org.springframework.security.config.BeanIds;
    +import org.springframework.security.config.MockServletContext;
     import org.springframework.security.config.util.InMemoryXmlWebApplicationContext;
     import org.springframework.test.context.web.GenericXmlWebContextLoader;
     import org.springframework.test.web.servlet.MockMvc;
    @@ -129,15 +129,15 @@ private SpringTestContext addFilter(Filter filter) {
     
     	public ConfigurableWebApplicationContext getContext() {
     		if (!this.context.isRunning()) {
    -			this.context.setServletContext(new MockServletContext());
    +			this.context.setServletContext(MockServletContext.mvc());
     			this.context.setServletConfig(new MockServletConfig());
     			this.context.refresh();
     		}
     		return this.context;
     	}
     
     	public void autowire() {
    -		this.context.setServletContext(new MockServletContext());
    +		this.context.setServletContext(MockServletContext.mvc());
     		this.context.setServletConfig(new MockServletConfig());
     		for (Consumer<ConfigurableWebApplicationContext> postProcessor : this.postProcessors) {
     			postProcessor.accept(this.context);
    
df239b6448cc

Improve RequestMatcher Validation

3 files changed · +148 17
  • config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.java+53 12 modified
    @@ -19,8 +19,11 @@
     import java.util.ArrayList;
     import java.util.Arrays;
     import java.util.List;
    +import java.util.Map;
     
     import javax.servlet.DispatcherType;
    +import javax.servlet.ServletContext;
    +import javax.servlet.ServletRegistration;
     
     import org.springframework.beans.factory.NoSuchBeanDefinitionException;
     import org.springframework.context.ApplicationContext;
    @@ -36,6 +39,7 @@
     import org.springframework.security.web.util.matcher.RequestMatcher;
     import org.springframework.util.Assert;
     import org.springframework.util.ClassUtils;
    +import org.springframework.web.context.WebApplicationContext;
     import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
     
     /**
    @@ -297,14 +301,47 @@ public C requestMatchers(RequestMatcher... requestMatchers) {
     	 * @since 5.8
     	 */
     	public C requestMatchers(HttpMethod method, String... patterns) {
    -		List<RequestMatcher> matchers = new ArrayList<>();
    -		if (mvcPresent) {
    -			matchers.addAll(createMvcMatchers(method, patterns));
    +		if (!mvcPresent) {
    +			return requestMatchers(RequestMatchers.antMatchersAsArray(method, patterns));
    +		}
    +		if (!(this.context instanceof WebApplicationContext)) {
    +			return requestMatchers(RequestMatchers.antMatchersAsArray(method, patterns));
    +		}
    +		WebApplicationContext context = (WebApplicationContext) this.context;
    +		ServletContext servletContext = context.getServletContext();
    +		if (servletContext == null) {
    +			return requestMatchers(RequestMatchers.antMatchersAsArray(method, patterns));
    +		}
    +		Map<String, ? extends ServletRegistration> registrations = servletContext.getServletRegistrations();
    +		if (registrations == null) {
    +			return requestMatchers(RequestMatchers.antMatchersAsArray(method, patterns));
    +		}
    +		if (!hasDispatcherServlet(registrations)) {
    +			return requestMatchers(RequestMatchers.antMatchersAsArray(method, patterns));
     		}
    -		else {
    -			matchers.addAll(RequestMatchers.antMatchers(method, patterns));
    +		Assert.isTrue(registrations.size() == 1,
    +				"This method cannot decide whether these patterns are Spring MVC patterns or not. If this endpoint is a Spring MVC endpoint, please use requestMatchers(MvcRequestMatcher); otherwise, please use requestMatchers(AntPathRequestMatcher).");
    +		return requestMatchers(createMvcMatchers(method, patterns).toArray(new RequestMatcher[0]));
    +	}
    +
    +	private boolean hasDispatcherServlet(Map<String, ? extends ServletRegistration> registrations) {
    +		if (registrations == null) {
    +			return false;
    +		}
    +		Class<?> dispatcherServlet = ClassUtils.resolveClassName("org.springframework.web.servlet.DispatcherServlet",
    +				null);
    +		for (ServletRegistration registration : registrations.values()) {
    +			try {
    +				Class<?> clazz = Class.forName(registration.getClassName());
    +				if (dispatcherServlet.isAssignableFrom(clazz)) {
    +					return true;
    +				}
    +			}
    +			catch (ClassNotFoundException ex) {
    +				return false;
    +			}
     		}
    -		return requestMatchers(matchers.toArray(new RequestMatcher[0]));
    +		return false;
     	}
     
     	/**
    @@ -380,12 +417,7 @@ private RequestMatchers() {
     		 * @return a {@link List} of {@link AntPathRequestMatcher} instances
     		 */
     		static List<RequestMatcher> antMatchers(HttpMethod httpMethod, String... antPatterns) {
    -			String method = (httpMethod != null) ? httpMethod.toString() : null;
    -			List<RequestMatcher> matchers = new ArrayList<>();
    -			for (String pattern : antPatterns) {
    -				matchers.add(new AntPathRequestMatcher(pattern, method));
    -			}
    -			return matchers;
    +			return Arrays.asList(antMatchersAsArray(httpMethod, antPatterns));
     		}
     
     		/**
    @@ -399,6 +431,15 @@ static List<RequestMatcher> antMatchers(String... antPatterns) {
     			return antMatchers(null, antPatterns);
     		}
     
    +		static RequestMatcher[] antMatchersAsArray(HttpMethod httpMethod, String... antPatterns) {
    +			String method = (httpMethod != null) ? httpMethod.toString() : null;
    +			RequestMatcher[] matchers = new RequestMatcher[antPatterns.length];
    +			for (int index = 0; index < antPatterns.length; index++) {
    +				matchers[index] = new AntPathRequestMatcher(antPatterns[index], method);
    +			}
    +			return matchers;
    +		}
    +
     		/**
     		 * Create a {@link List} of {@link RegexRequestMatcher} instances.
     		 * @param httpMethod the {@link HttpMethod} to use or {@code null} for any
    
  • config/src/test/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistryTests.java+63 3 modified
    @@ -18,10 +18,16 @@
     
     import java.lang.reflect.Field;
     import java.lang.reflect.Modifier;
    +import java.util.LinkedHashMap;
     import java.util.List;
    +import java.util.Map;
     
     import javax.servlet.DispatcherType;
    +import javax.servlet.Servlet;
    +import javax.servlet.ServletContext;
    +import javax.servlet.ServletRegistration;
     
    +import org.jetbrains.annotations.NotNull;
     import org.junit.jupiter.api.BeforeEach;
     import org.junit.jupiter.api.Test;
     
    @@ -34,6 +40,8 @@
     import org.springframework.security.web.util.matcher.DispatcherTypeRequestMatcher;
     import org.springframework.security.web.util.matcher.RegexRequestMatcher;
     import org.springframework.security.web.util.matcher.RequestMatcher;
    +import org.springframework.web.context.WebApplicationContext;
    +import org.springframework.web.servlet.DispatcherServlet;
     
     import static org.assertj.core.api.Assertions.assertThat;
     import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
    @@ -56,12 +64,17 @@ public <O> O postProcess(O object) {
     
     	private TestRequestMatcherRegistry matcherRegistry;
     
    +	private WebApplicationContext context;
    +
     	@BeforeEach
     	public void setUp() {
     		this.matcherRegistry = new TestRequestMatcherRegistry();
    -		ApplicationContext context = mock(ApplicationContext.class);
    -		given(context.getBean(ObjectPostProcessor.class)).willReturn(NO_OP_OBJECT_POST_PROCESSOR);
    -		this.matcherRegistry.setApplicationContext(context);
    +		this.context = mock(WebApplicationContext.class);
    +		ServletContext servletContext = new MockServletContext();
    +		servletContext.addServlet("dispatcherServlet", DispatcherServlet.class);
    +		given(this.context.getBean(ObjectPostProcessor.class)).willReturn(NO_OP_OBJECT_POST_PROCESSOR);
    +		given(this.context.getServletContext()).willReturn(servletContext);
    +		this.matcherRegistry.setApplicationContext(this.context);
     	}
     
     	@Test
    @@ -184,6 +197,32 @@ public void requestMatchersWhenMvcPresentInClassPathAndMvcIntrospectorBeanNotAva
     						"Please ensure Spring Security & Spring MVC are configured in a shared ApplicationContext");
     	}
     
    +	@Test
    +	public void requestMatchersWhenNoDispatcherServletThenAntPathRequestMatcherType() {
    +		MockServletContext servletContext = new MockServletContext();
    +		given(this.context.getServletContext()).willReturn(servletContext);
    +		List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers("/**");
    +		assertThat(requestMatchers).isNotEmpty();
    +		assertThat(requestMatchers).hasSize(1);
    +		assertThat(requestMatchers.get(0)).isExactlyInstanceOf(AntPathRequestMatcher.class);
    +		servletContext.addServlet("servletOne", Servlet.class);
    +		servletContext.addServlet("servletTwo", Servlet.class);
    +		requestMatchers = this.matcherRegistry.requestMatchers("/**");
    +		assertThat(requestMatchers).isNotEmpty();
    +		assertThat(requestMatchers).hasSize(1);
    +		assertThat(requestMatchers.get(0)).isExactlyInstanceOf(AntPathRequestMatcher.class);
    +	}
    +
    +	@Test
    +	public void requestMatchersWhenAmbiguousServletsThenException() {
    +		MockServletContext servletContext = new MockServletContext();
    +		given(this.context.getServletContext()).willReturn(servletContext);
    +		servletContext.addServlet("dispatcherServlet", DispatcherServlet.class);
    +		servletContext.addServlet("servletTwo", Servlet.class);
    +		assertThatExceptionOfType(IllegalArgumentException.class)
    +				.isThrownBy(() -> this.matcherRegistry.requestMatchers("/**"));
    +	}
    +
     	private void mockMvcIntrospector(boolean isPresent) {
     		ApplicationContext context = this.matcherRegistry.getApplicationContext();
     		given(context.containsBean("mvcHandlerMappingIntrospector")).willReturn(isPresent);
    @@ -217,4 +256,25 @@ protected List<RequestMatcher> chainRequestMatchers(List<RequestMatcher> request
     
     	}
     
    +	private static class MockServletContext extends org.springframework.mock.web.MockServletContext {
    +
    +		private final Map<String, ServletRegistration> registrations = new LinkedHashMap<>();
    +
    +		@NotNull
    +		@Override
    +		public ServletRegistration.Dynamic addServlet(@NotNull String servletName, Class<? extends Servlet> clazz) {
    +			ServletRegistration.Dynamic dynamic = mock(ServletRegistration.Dynamic.class);
    +			given(dynamic.getClassName()).willReturn(clazz.getName());
    +			this.registrations.put(servletName, dynamic);
    +			return dynamic;
    +		}
    +
    +		@NotNull
    +		@Override
    +		public Map<String, ? extends ServletRegistration> getServletRegistrations() {
    +			return this.registrations;
    +		}
    +
    +	}
    +
     }
    
  • config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecuritySecurityMatchersTests.java+32 2 modified
    @@ -18,9 +18,14 @@
     
     import java.lang.reflect.Field;
     import java.lang.reflect.Modifier;
    +import java.util.LinkedHashMap;
    +import java.util.Map;
     
    +import javax.servlet.Servlet;
    +import javax.servlet.ServletRegistration;
     import javax.servlet.http.HttpServletResponse;
     
    +import org.jetbrains.annotations.NotNull;
     import org.junit.jupiter.api.AfterEach;
     import org.junit.jupiter.api.BeforeEach;
     import org.junit.jupiter.api.Test;
    @@ -34,7 +39,6 @@
     import org.springframework.mock.web.MockFilterChain;
     import org.springframework.mock.web.MockHttpServletRequest;
     import org.springframework.mock.web.MockHttpServletResponse;
    -import org.springframework.mock.web.MockServletContext;
     import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
     import org.springframework.security.config.annotation.web.builders.HttpSecurity;
     import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    @@ -48,12 +52,15 @@
     import org.springframework.web.bind.annotation.RequestMapping;
     import org.springframework.web.bind.annotation.RestController;
     import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
    +import org.springframework.web.servlet.DispatcherServlet;
     import org.springframework.web.servlet.config.annotation.EnableWebMvc;
     import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
     import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
     import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
     
     import static org.assertj.core.api.Assertions.assertThat;
    +import static org.mockito.BDDMockito.given;
    +import static org.mockito.Mockito.mock;
     import static org.springframework.security.config.Customizer.withDefaults;
     
     /**
    @@ -233,7 +240,9 @@ public void securityMatchersWhenMultiMvcMatcherThenAllPathsAreDenied() throws Ex
     	public void loadConfig(Class<?>... configs) {
     		this.context = new AnnotationConfigWebApplicationContext();
     		this.context.register(configs);
    -		this.context.setServletContext(new MockServletContext());
    +		MockServletContext servletContext = new MockServletContext();
    +		servletContext.addServlet("dispatcherServlet", DispatcherServlet.class);
    +		this.context.setServletContext(servletContext);
     		this.context.refresh();
     		this.context.getAutowireCapableBeanFactory().autowireBean(this);
     	}
    @@ -564,4 +573,25 @@ public void configurePathMatch(PathMatchConfigurer configurer) {
     
     	}
     
    +	private static class MockServletContext extends org.springframework.mock.web.MockServletContext {
    +
    +		private final Map<String, ServletRegistration> registrations = new LinkedHashMap<>();
    +
    +		@NotNull
    +		@Override
    +		public ServletRegistration.Dynamic addServlet(@NotNull String servletName, Class<? extends Servlet> clazz) {
    +			ServletRegistration.Dynamic dynamic = mock(ServletRegistration.Dynamic.class);
    +			given(dynamic.getClassName()).willReturn(clazz.getName());
    +			this.registrations.put(servletName, dynamic);
    +			return dynamic;
    +		}
    +
    +		@NotNull
    +		@Override
    +		public Map<String, ? extends ServletRegistration> getServletRegistrations() {
    +			return this.registrations;
    +		}
    +
    +	}
    +
     }
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.