From 63e0a56bee57c5b2b8e0d3fafc24e7fccae82daf Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Sun, 20 Jul 2025 22:57:06 -0600 Subject: [PATCH] Add setBasePath Originally, it was thought that this feature would be rather uncommon; however, given some feedback from the Boot team, it makes sense to make this easier to configure. Of specific note is migrating from an earlier version were the servlet path did not need to be specified in authorizeHttpRequests. Since it does in 7, this will be a significant migration for those who have a servlet path configured. This setter simplifies that a great deal, including simplifying Boot's support of it. Closes gh-17579 --- ...tternRequestMatcherBuilderFactoryBean.java | 26 +++++++++++++++++-- ...RequestMatcherBuilderFactoryBeanTests.java | 13 ++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/web/PathPatternRequestMatcherBuilderFactoryBean.java b/config/src/main/java/org/springframework/security/config/web/PathPatternRequestMatcherBuilderFactoryBean.java index 391b991747..8cb63b754a 100644 --- a/config/src/main/java/org/springframework/security/config/web/PathPatternRequestMatcherBuilderFactoryBean.java +++ b/config/src/main/java/org/springframework/security/config/web/PathPatternRequestMatcherBuilderFactoryBean.java @@ -46,6 +46,8 @@ public final class PathPatternRequestMatcherBuilderFactoryBean implements private final PathPatternParser parser; + private String basePath; + private ApplicationContext context; private String beanName; @@ -78,23 +80,43 @@ public final class PathPatternRequestMatcherBuilderFactoryBean implements public PathPatternRequestMatcher.Builder getObject() throws Exception { if (!this.context.containsBean(MVC_PATTERN_PARSER_BEAN_NAME)) { PathPatternParser parser = (this.parser != null) ? this.parser : PathPatternParser.defaultInstance; - return PathPatternRequestMatcher.withPathPatternParser(parser); + return withPathPatternParser(parser); } PathPatternParser mvc = this.context.getBean(MVC_PATTERN_PARSER_BEAN_NAME, PathPatternParser.class); PathPatternParser parser = (this.parser != null) ? this.parser : mvc; if (mvc.equals(parser)) { - return PathPatternRequestMatcher.withPathPatternParser(parser); + return withPathPatternParser(parser); } throw new IllegalArgumentException("Spring Security and Spring MVC must use the same path pattern parser. " + "To have Spring Security use Spring MVC's [" + describe(mvc, MVC_PATTERN_PARSER_BEAN_NAME) + "] simply publish this bean [" + describe(this, this.beanName) + "] using its default constructor"); } + private PathPatternRequestMatcher.Builder withPathPatternParser(PathPatternParser parser) { + if (this.basePath == null) { + return PathPatternRequestMatcher.withPathPatternParser(parser); + } + else { + return PathPatternRequestMatcher.withPathPatternParser(parser).basePath(this.basePath); + } + } + @Override public Class getObjectType() { return PathPatternRequestMatcher.Builder.class; } + /** + * Use this as the base path for patterns built by the resulting + * {@link PathPatternRequestMatcher.Builder} instance + * @param basePath the base path to use + * @since 7.0 + * @see PathPatternRequestMatcher.Builder#basePath(String) + */ + public void setBasePath(String basePath) { + this.basePath = basePath; + } + @Override public void setApplicationContext(ApplicationContext context) throws BeansException { this.context = context; diff --git a/config/src/test/java/org/springframework/security/config/web/PathPatternRequestMatcherBuilderFactoryBeanTests.java b/config/src/test/java/org/springframework/security/config/web/PathPatternRequestMatcherBuilderFactoryBeanTests.java index 79819e1185..09dd46531c 100644 --- a/config/src/test/java/org/springframework/security/config/web/PathPatternRequestMatcherBuilderFactoryBeanTests.java +++ b/config/src/test/java/org/springframework/security/config/web/PathPatternRequestMatcherBuilderFactoryBeanTests.java @@ -22,9 +22,11 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.context.support.GenericApplicationContext; +import org.springframework.security.web.servlet.TestMockHttpServletRequests; import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; import org.springframework.web.util.pattern.PathPatternParser; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -75,6 +77,17 @@ class PathPatternRequestMatcherBuilderFactoryBeanTests { verify(mvc).parse("/path/**"); } + @Test + void getObjectWhenBasePathThenUses() throws Exception { + PathPatternRequestMatcherBuilderFactoryBean factoryBean = new PathPatternRequestMatcherBuilderFactoryBean(); + factoryBean.setApplicationContext(this.context); + factoryBean.setBasePath("/mvc"); + PathPatternRequestMatcher.Builder builder = factoryBean.getObject(); + PathPatternRequestMatcher matcher = builder.matcher("/path/**"); + assertThat(matcher.matches(TestMockHttpServletRequests.get("/mvc/path/123").build())).isTrue(); + assertThat(matcher.matches(TestMockHttpServletRequests.get("/path/123").build())).isFalse(); + } + PathPatternRequestMatcherBuilderFactoryBean factoryBean() { PathPatternRequestMatcherBuilderFactoryBean factoryBean = new PathPatternRequestMatcherBuilderFactoryBean(); factoryBean.setApplicationContext(this.context);