Move PathPatternRequestMatcher.Builder to Shared Object
This commit changes the DSL to look for a shared object instead of publishing a bean for PathPatternRequestMatcher.Builder. Closes gh-17746
This commit is contained in:
		
							parent
							
								
									006f638c0a
								
							
						
					
					
						commit
						aeb2dbc2b6
					
				|  | @ -27,11 +27,13 @@ import org.apache.commons.logging.LogFactory; | |||
| import org.springframework.context.ApplicationContext; | ||||
| import org.springframework.http.HttpMethod; | ||||
| import org.springframework.lang.Nullable; | ||||
| import org.springframework.security.config.web.PathPatternRequestMatcherBuilderFactoryBean; | ||||
| import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; | ||||
| import org.springframework.security.web.util.matcher.AnyRequestMatcher; | ||||
| import org.springframework.security.web.util.matcher.DispatcherTypeRequestMatcher; | ||||
| import org.springframework.security.web.util.matcher.RequestMatcher; | ||||
| import org.springframework.util.Assert; | ||||
| import org.springframework.util.function.ThrowingSupplier; | ||||
| 
 | ||||
| /** | ||||
|  * A base class for registering {@link RequestMatcher}'s. For example, it might allow for | ||||
|  | @ -52,6 +54,8 @@ public abstract class AbstractRequestMatcherRegistry<C> { | |||
| 
 | ||||
| 	private final Log logger = LogFactory.getLog(getClass()); | ||||
| 
 | ||||
| 	private PathPatternRequestMatcher.Builder requestMatcherBuilder; | ||||
| 
 | ||||
| 	protected final void setApplicationContext(ApplicationContext context) { | ||||
| 		this.context = context; | ||||
| 	} | ||||
|  | @ -140,7 +144,7 @@ public abstract class AbstractRequestMatcherRegistry<C> { | |||
| 					+ "Spring Security, leaving out the leading slash will result in an exception."); | ||||
| 		} | ||||
| 		Assert.state(!this.anyRequestConfigured, "Can't configure requestMatchers after anyRequest"); | ||||
| 		PathPatternRequestMatcher.Builder builder = this.context.getBean(PathPatternRequestMatcher.Builder.class); | ||||
| 		PathPatternRequestMatcher.Builder builder = getRequestMatcherBuilder(); | ||||
| 		List<RequestMatcher> matchers = new ArrayList<>(); | ||||
| 		for (String pattern : patterns) { | ||||
| 			matchers.add(builder.matcher(method, pattern)); | ||||
|  | @ -148,6 +152,23 @@ public abstract class AbstractRequestMatcherRegistry<C> { | |||
| 		return requestMatchers(matchers.toArray(new RequestMatcher[0])); | ||||
| 	} | ||||
| 
 | ||||
| 	private PathPatternRequestMatcher.Builder getRequestMatcherBuilder() { | ||||
| 		if (this.requestMatcherBuilder != null) { | ||||
| 			return this.requestMatcherBuilder; | ||||
| 		} | ||||
| 		this.requestMatcherBuilder = this.context.getBeanProvider(PathPatternRequestMatcher.Builder.class) | ||||
| 			.getIfUnique(() -> constructRequestMatcherBuilder(this.context)); | ||||
| 		return this.requestMatcherBuilder; | ||||
| 	} | ||||
| 
 | ||||
| 	private PathPatternRequestMatcher.Builder constructRequestMatcherBuilder(ApplicationContext context) { | ||||
| 		PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder = new PathPatternRequestMatcherBuilderFactoryBean(); | ||||
| 		requestMatcherBuilder.setApplicationContext(context); | ||||
| 		requestMatcherBuilder.setBeanFactory(context.getAutowireCapableBeanFactory()); | ||||
| 		requestMatcherBuilder.setBeanName(requestMatcherBuilder.toString()); | ||||
| 		return ThrowingSupplier.of(requestMatcherBuilder::getObject).get(); | ||||
| 	} | ||||
| 
 | ||||
| 	private boolean anyPathsDontStartWithLeadingSlash(String... patterns) { | ||||
| 		for (String pattern : patterns) { | ||||
| 			if (!pattern.startsWith("/")) { | ||||
|  |  | |||
|  | @ -2058,7 +2058,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul | |||
| 	 */ | ||||
| 	public HttpSecurity securityMatcher(String... patterns) { | ||||
| 		List<RequestMatcher> matchers = new ArrayList<>(); | ||||
| 		PathPatternRequestMatcher.Builder builder = getContext().getBean(PathPatternRequestMatcher.Builder.class); | ||||
| 		PathPatternRequestMatcher.Builder builder = getSharedObject(PathPatternRequestMatcher.Builder.class); | ||||
| 		for (String pattern : patterns) { | ||||
| 			matchers.add(builder.matcher(pattern)); | ||||
| 		} | ||||
|  |  | |||
|  | @ -1,31 +0,0 @@ | |||
| /* | ||||
|  * Copyright 2004-present 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.annotation.web.configuration; | ||||
| 
 | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Fallback; | ||||
| import org.springframework.security.config.web.PathPatternRequestMatcherBuilderFactoryBean; | ||||
| 
 | ||||
| class AuthorizationConfiguration { | ||||
| 
 | ||||
| 	@Bean | ||||
| 	@Fallback | ||||
| 	PathPatternRequestMatcherBuilderFactoryBean pathPatternRequestMatcherBuilder() { | ||||
| 		return new PathPatternRequestMatcherBuilderFactoryBean(); | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  | @ -83,7 +83,7 @@ import org.springframework.security.web.SecurityFilterChain; | |||
| @Target(ElementType.TYPE) | ||||
| @Documented | ||||
| @Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class, | ||||
| 		HttpSecurityConfiguration.class, ObservationImportSelector.class, AuthorizationConfiguration.class }) | ||||
| 		HttpSecurityConfiguration.class, ObservationImportSelector.class }) | ||||
| @EnableGlobalAuthentication | ||||
| public @interface EnableWebSecurity { | ||||
| 
 | ||||
|  |  | |||
|  | @ -38,12 +38,15 @@ import org.springframework.security.config.annotation.authentication.configurers | |||
| import org.springframework.security.config.annotation.web.builders.HttpSecurity; | ||||
| import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; | ||||
| import org.springframework.security.config.annotation.web.configurers.DefaultLoginPageConfigurer; | ||||
| import org.springframework.security.config.web.PathPatternRequestMatcherBuilderFactoryBean; | ||||
| import org.springframework.security.core.context.SecurityContextHolder; | ||||
| import org.springframework.security.core.context.SecurityContextHolderStrategy; | ||||
| import org.springframework.security.core.userdetails.UserDetailsService; | ||||
| import org.springframework.security.crypto.factory.PasswordEncoderFactories; | ||||
| import org.springframework.security.crypto.password.PasswordEncoder; | ||||
| import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter; | ||||
| import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; | ||||
| import org.springframework.util.function.ThrowingSupplier; | ||||
| import org.springframework.web.accept.ContentNegotiationStrategy; | ||||
| import org.springframework.web.accept.HeaderContentNegotiationStrategy; | ||||
| import org.springframework.web.cors.UrlBasedCorsConfigurationSource; | ||||
|  | @ -161,9 +164,18 @@ class HttpSecurityConfiguration { | |||
| 		Map<Class<?>, Object> sharedObjects = new HashMap<>(); | ||||
| 		sharedObjects.put(ApplicationContext.class, this.context); | ||||
| 		sharedObjects.put(ContentNegotiationStrategy.class, this.contentNegotiationStrategy); | ||||
| 		sharedObjects.put(PathPatternRequestMatcher.Builder.class, constructRequestMatcherBuilder(this.context)); | ||||
| 		return sharedObjects; | ||||
| 	} | ||||
| 
 | ||||
| 	private PathPatternRequestMatcher.Builder constructRequestMatcherBuilder(ApplicationContext context) { | ||||
| 		PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder = new PathPatternRequestMatcherBuilderFactoryBean(); | ||||
| 		requestMatcherBuilder.setApplicationContext(context); | ||||
| 		requestMatcherBuilder.setBeanFactory(context.getAutowireCapableBeanFactory()); | ||||
| 		requestMatcherBuilder.setBeanName(requestMatcherBuilder.toString()); | ||||
| 		return ThrowingSupplier.of(requestMatcherBuilder::getObject).get(); | ||||
| 	} | ||||
| 
 | ||||
| 	static class DefaultPasswordEncoderAuthenticationManagerBuilder extends AuthenticationManagerBuilder { | ||||
| 
 | ||||
| 		private PasswordEncoder defaultPasswordEncoder; | ||||
|  |  | |||
|  | @ -39,8 +39,6 @@ public abstract class AbstractHttpConfigurer<T extends AbstractHttpConfigurer<T, | |||
| 
 | ||||
| 	private SecurityContextHolderStrategy securityContextHolderStrategy; | ||||
| 
 | ||||
| 	private PathPatternRequestMatcher.Builder requestMatcherBuilder; | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Disables the {@link AbstractHttpConfigurer} by removing it. After doing so a fresh | ||||
| 	 * version of the configuration can be applied. | ||||
|  | @ -69,12 +67,7 @@ public abstract class AbstractHttpConfigurer<T extends AbstractHttpConfigurer<T, | |||
| 	} | ||||
| 
 | ||||
| 	protected PathPatternRequestMatcher.Builder getRequestMatcherBuilder() { | ||||
| 		if (this.requestMatcherBuilder != null) { | ||||
| 			return this.requestMatcherBuilder; | ||||
| 		} | ||||
| 		ApplicationContext context = getBuilder().getSharedObject(ApplicationContext.class); | ||||
| 		this.requestMatcherBuilder = context.getBean(PathPatternRequestMatcher.Builder.class); | ||||
| 		return this.requestMatcherBuilder; | ||||
| 		return getBuilder().getSharedObject(PathPatternRequestMatcher.Builder.class); | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -17,6 +17,7 @@ | |||
| package org.springframework.security.config.annotation.web; | ||||
| 
 | ||||
| import java.util.List; | ||||
| import java.util.stream.Stream; | ||||
| 
 | ||||
| import jakarta.servlet.DispatcherType; | ||||
| import org.junit.jupiter.api.BeforeEach; | ||||
|  | @ -68,8 +69,8 @@ public class AbstractRequestMatcherRegistryTests { | |||
| 		ObjectProvider<ObjectPostProcessor<Object>> given = this.context.getBeanProvider(type); | ||||
| 		given(given).willReturn(postProcessors); | ||||
| 		given(postProcessors.getObject()).willReturn(NO_OP_OBJECT_POST_PROCESSOR); | ||||
| 		given(this.context.getBean(PathPatternRequestMatcher.Builder.class)) | ||||
| 			.willReturn(PathPatternRequestMatcher.withDefaults()); | ||||
| 		given(this.context.getBeanProvider(PathPatternRequestMatcher.Builder.class)) | ||||
| 			.willReturn(new SingleObjectProvider<>(PathPatternRequestMatcher.withDefaults())); | ||||
| 		this.matcherRegistry.setApplicationContext(this.context); | ||||
| 	} | ||||
| 
 | ||||
|  | @ -165,4 +166,19 @@ public class AbstractRequestMatcherRegistryTests { | |||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	private static final class SingleObjectProvider<T> implements ObjectProvider<T> { | ||||
| 
 | ||||
| 		private final T object; | ||||
| 
 | ||||
| 		private SingleObjectProvider(T object) { | ||||
| 			this.object = object; | ||||
| 		} | ||||
| 
 | ||||
| 		@Override | ||||
| 		public Stream<T> stream() { | ||||
| 			return Stream.of(this.object); | ||||
| 		} | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -54,6 +54,7 @@ import org.springframework.security.config.core.GrantedAuthorityDefaults; | |||
| import org.springframework.security.config.observation.SecurityObservationSettings; | ||||
| import org.springframework.security.config.test.SpringTestContext; | ||||
| import org.springframework.security.config.test.SpringTestContextExtension; | ||||
| import org.springframework.security.config.web.PathPatternRequestMatcherBuilderFactoryBean; | ||||
| import org.springframework.security.core.Authentication; | ||||
| import org.springframework.security.core.authority.AuthorityUtils; | ||||
| import org.springframework.security.core.authority.SimpleGrantedAuthority; | ||||
|  | @ -1051,12 +1052,19 @@ public class AuthorizeHttpRequestsConfigurerTests { | |||
| 	@EnableWebSecurity | ||||
| 	static class ServletPathConfig { | ||||
| 
 | ||||
| 		@Bean | ||||
| 		PathPatternRequestMatcherBuilderFactoryBean requesMatcherBuilder() { | ||||
| 			PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean(); | ||||
| 			bean.setBasePath("/spring"); | ||||
| 			return bean; | ||||
| 		} | ||||
| 
 | ||||
| 		@Bean | ||||
| 		SecurityFilterChain filterChain(HttpSecurity http, PathPatternRequestMatcher.Builder builder) throws Exception { | ||||
| 			// @formatter:off | ||||
| 			return http | ||||
| 					.authorizeHttpRequests((authorize) -> authorize | ||||
| 						.requestMatchers(builder.basePath("/spring").matcher("/")).hasRole("ADMIN") | ||||
| 						.requestMatchers(builder.matcher("/")).hasRole("ADMIN") | ||||
| 					) | ||||
| 					.build(); | ||||
| 			// @formatter:on | ||||
|  |  | |||
|  | @ -32,6 +32,7 @@ import org.springframework.mock.web.MockHttpServletResponse; | |||
| import org.springframework.mock.web.MockServletContext; | ||||
| import org.springframework.security.config.annotation.web.builders.HttpSecurity; | ||||
| import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; | ||||
| import org.springframework.security.config.web.PathPatternRequestMatcherBuilderFactoryBean; | ||||
| import org.springframework.security.core.userdetails.UserDetailsService; | ||||
| import org.springframework.security.provisioning.InMemoryUserDetailsManager; | ||||
| import org.springframework.security.web.FilterChainProxy; | ||||
|  | @ -157,6 +158,11 @@ public class HttpSecurityRequestMatchersTests { | |||
| 	@EnableWebMvc | ||||
| 	static class MultiMvcMatcherInLambdaConfig { | ||||
| 
 | ||||
| 		@Bean | ||||
| 		PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() { | ||||
| 			return new PathPatternRequestMatcherBuilderFactoryBean(); | ||||
| 		} | ||||
| 
 | ||||
| 		@Bean | ||||
| 		@Order(Ordered.HIGHEST_PRECEDENCE) | ||||
| 		SecurityFilterChain first(HttpSecurity http, PathPatternRequestMatcher.Builder builder) throws Exception { | ||||
|  | @ -204,6 +210,11 @@ public class HttpSecurityRequestMatchersTests { | |||
| 	@EnableWebMvc | ||||
| 	static class MultiMvcMatcherConfig { | ||||
| 
 | ||||
| 		@Bean | ||||
| 		PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() { | ||||
| 			return new PathPatternRequestMatcherBuilderFactoryBean(); | ||||
| 		} | ||||
| 
 | ||||
| 		@Bean | ||||
| 		@Order(Ordered.HIGHEST_PRECEDENCE) | ||||
| 		SecurityFilterChain first(HttpSecurity http, PathPatternRequestMatcher.Builder builder) throws Exception { | ||||
|  | @ -249,6 +260,11 @@ public class HttpSecurityRequestMatchersTests { | |||
| 	@EnableWebMvc | ||||
| 	static class MvcMatcherConfig { | ||||
| 
 | ||||
| 		@Bean | ||||
| 		PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() { | ||||
| 			return new PathPatternRequestMatcherBuilderFactoryBean(); | ||||
| 		} | ||||
| 
 | ||||
| 		@Bean | ||||
| 		SecurityFilterChain filterChain(HttpSecurity http, PathPatternRequestMatcher.Builder builder) throws Exception { | ||||
| 			// @formatter:off | ||||
|  | @ -283,6 +299,11 @@ public class HttpSecurityRequestMatchersTests { | |||
| 	@EnableWebMvc | ||||
| 	static class RequestMatchersMvcMatcherConfig { | ||||
| 
 | ||||
| 		@Bean | ||||
| 		PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() { | ||||
| 			return new PathPatternRequestMatcherBuilderFactoryBean(); | ||||
| 		} | ||||
| 
 | ||||
| 		@Bean | ||||
| 		SecurityFilterChain filterChain(HttpSecurity http, PathPatternRequestMatcher.Builder builder) throws Exception { | ||||
| 			// @formatter:off | ||||
|  | @ -318,6 +339,11 @@ public class HttpSecurityRequestMatchersTests { | |||
| 	@EnableWebMvc | ||||
| 	static class RequestMatchersMvcMatcherInLambdaConfig { | ||||
| 
 | ||||
| 		@Bean | ||||
| 		PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() { | ||||
| 			return new PathPatternRequestMatcherBuilderFactoryBean(); | ||||
| 		} | ||||
| 
 | ||||
| 		@Bean | ||||
| 		SecurityFilterChain filterChain(HttpSecurity http, PathPatternRequestMatcher.Builder builder) throws Exception { | ||||
| 			// @formatter:off | ||||
|  | @ -350,6 +376,11 @@ public class HttpSecurityRequestMatchersTests { | |||
| 	@EnableWebMvc | ||||
| 	static class RequestMatchersMvcMatcherServeltPathConfig { | ||||
| 
 | ||||
| 		@Bean | ||||
| 		PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() { | ||||
| 			return new PathPatternRequestMatcherBuilderFactoryBean(); | ||||
| 		} | ||||
| 
 | ||||
| 		@Bean | ||||
| 		SecurityFilterChain filterChain(HttpSecurity http, PathPatternRequestMatcher.Builder builder) throws Exception { | ||||
| 			// @formatter:off | ||||
|  | @ -386,6 +417,11 @@ public class HttpSecurityRequestMatchersTests { | |||
| 	@EnableWebMvc | ||||
| 	static class RequestMatchersMvcMatcherServletPathInLambdaConfig { | ||||
| 
 | ||||
| 		@Bean | ||||
| 		PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() { | ||||
| 			return new PathPatternRequestMatcherBuilderFactoryBean(); | ||||
| 		} | ||||
| 
 | ||||
| 		@Bean | ||||
| 		SecurityFilterChain filterChain(HttpSecurity http, PathPatternRequestMatcher.Builder builder) throws Exception { | ||||
| 			// @formatter:off | ||||
|  |  | |||
|  | @ -32,6 +32,7 @@ import org.springframework.mock.web.MockHttpServletRequest; | |||
| import org.springframework.mock.web.MockHttpServletResponse; | ||||
| import org.springframework.security.config.annotation.web.builders.HttpSecurity; | ||||
| import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; | ||||
| import org.springframework.security.config.web.PathPatternRequestMatcherBuilderFactoryBean; | ||||
| import org.springframework.security.core.userdetails.User; | ||||
| import org.springframework.security.core.userdetails.UserDetails; | ||||
| import org.springframework.security.core.userdetails.UserDetailsService; | ||||
|  | @ -354,14 +355,20 @@ public class HttpSecuritySecurityMatchersTests { | |||
| 	@Import(UsersConfig.class) | ||||
| 	static class SecurityMatchersMvcMatcherServletPathConfig { | ||||
| 
 | ||||
| 		@Bean | ||||
| 		PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() { | ||||
| 			PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean(); | ||||
| 			bean.setBasePath("/spring"); | ||||
| 			return bean; | ||||
| 		} | ||||
| 
 | ||||
| 		@Bean | ||||
| 		SecurityFilterChain appSecurity(HttpSecurity http, PathPatternRequestMatcher.Builder builder) throws Exception { | ||||
| 			PathPatternRequestMatcher.Builder spring = builder.basePath("/spring"); | ||||
| 			// @formatter:off | ||||
| 			http | ||||
| 				.securityMatchers((security) -> security | ||||
| 					.requestMatchers(spring.matcher("/path")) | ||||
| 					.requestMatchers(spring.matcher("/never-match")) | ||||
| 					.requestMatchers(builder.matcher("/path")) | ||||
| 					.requestMatchers(builder.matcher("/never-match")) | ||||
| 				) | ||||
| 				.httpBasic(withDefaults()) | ||||
| 				.authorizeHttpRequests((authorize) -> authorize | ||||
|  | @ -388,14 +395,20 @@ public class HttpSecuritySecurityMatchersTests { | |||
| 	@Import(UsersConfig.class) | ||||
| 	static class SecurityMatchersMvcMatcherServletPathInLambdaConfig { | ||||
| 
 | ||||
| 		@Bean | ||||
| 		PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() { | ||||
| 			PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean(); | ||||
| 			bean.setBasePath("/spring"); | ||||
| 			return bean; | ||||
| 		} | ||||
| 
 | ||||
| 		@Bean | ||||
| 		SecurityFilterChain appSecurity(HttpSecurity http, PathPatternRequestMatcher.Builder builder) throws Exception { | ||||
| 			PathPatternRequestMatcher.Builder spring = builder.basePath("/spring"); | ||||
| 			// @formatter:off | ||||
| 			http | ||||
| 				.securityMatchers((matchers) -> matchers | ||||
| 					.requestMatchers(spring.matcher("/path")) | ||||
| 					.requestMatchers(spring.matcher("/never-match")) | ||||
| 					.requestMatchers(builder.matcher("/path")) | ||||
| 					.requestMatchers(builder.matcher("/never-match")) | ||||
| 				) | ||||
| 				.httpBasic(withDefaults()) | ||||
| 				.authorizeHttpRequests((authorize) -> authorize | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue