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);