Skip to content

Commit 7d301f8

Browse files
committed
Add Opt-in PathPattern Strategy
Closes gh-16573
1 parent 588220a commit 7d301f8

18 files changed

+589
-69
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.java

+9-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -219,9 +219,14 @@ public C requestMatchers(HttpMethod method, String... patterns) {
219219
}
220220
List<RequestMatcher> matchers = new ArrayList<>();
221221
for (String pattern : patterns) {
222-
AntPathRequestMatcher ant = new AntPathRequestMatcher(pattern, (method != null) ? method.name() : null);
223-
MvcRequestMatcher mvc = createMvcMatchers(method, pattern).get(0);
224-
matchers.add(new DeferredRequestMatcher((c) -> resolve(ant, mvc, c), mvc, ant));
222+
if (RequestMatcherFactory.usesPathPatterns()) {
223+
matchers.add(RequestMatcherFactory.matcher(method, pattern));
224+
}
225+
else {
226+
AntPathRequestMatcher ant = new AntPathRequestMatcher(pattern, (method != null) ? method.name() : null);
227+
MvcRequestMatcher mvc = createMvcMatchers(method, pattern).get(0);
228+
matchers.add(new DeferredRequestMatcher((c) -> resolve(ant, mvc, c), mvc, ant));
229+
}
225230
}
226231
return requestMatchers(matchers.toArray(new RequestMatcher[0]));
227232
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright 2002-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.config.annotation.web;
18+
19+
import org.springframework.context.ApplicationContext;
20+
import org.springframework.http.HttpMethod;
21+
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
22+
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
23+
import org.springframework.security.web.util.matcher.RequestMatcher;
24+
25+
/**
26+
* This utility exists only to facilitate applications opting into using path patterns in
27+
* the HttpSecurity DSL. It is for internal use only.
28+
*
29+
* @deprecated
30+
*/
31+
@Deprecated(forRemoval = true)
32+
public final class RequestMatcherFactory {
33+
34+
private static PathPatternRequestMatcher.Builder builder;
35+
36+
public static void setApplicationContext(ApplicationContext context) {
37+
builder = context.getBeanProvider(PathPatternRequestMatcher.Builder.class).getIfUnique();
38+
}
39+
40+
public static boolean usesPathPatterns() {
41+
return builder != null;
42+
}
43+
44+
public static RequestMatcher matcher(String path) {
45+
return matcher(null, path);
46+
}
47+
48+
public static RequestMatcher matcher(HttpMethod method, String path) {
49+
if (builder != null) {
50+
return builder.matcher(method, path);
51+
}
52+
return new AntPathRequestMatcher(path, (method != null) ? method.name() : null);
53+
}
54+
55+
private RequestMatcherFactory() {
56+
57+
}
58+
59+
}

config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java

+18-19
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -45,6 +45,7 @@
4545
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
4646
import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
4747
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
48+
import org.springframework.security.config.annotation.web.RequestMatcherFactory;
4849
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
4950
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
5051
import org.springframework.security.config.annotation.web.configurers.AnonymousConfigurer;
@@ -3684,11 +3685,17 @@ public HttpSecurity securityMatcher(RequestMatcher requestMatcher) {
36843685
* @see MvcRequestMatcher
36853686
*/
36863687
public HttpSecurity securityMatcher(String... patterns) {
3687-
if (mvcPresent) {
3688-
this.requestMatcher = new OrRequestMatcher(createMvcMatchers(patterns));
3689-
return this;
3688+
List<RequestMatcher> matchers = new ArrayList<>();
3689+
for (String pattern : patterns) {
3690+
if (RequestMatcherFactory.usesPathPatterns()) {
3691+
matchers.add(RequestMatcherFactory.matcher(pattern));
3692+
}
3693+
else {
3694+
RequestMatcher matcher = mvcPresent ? createMvcMatcher(pattern) : createAntMatcher(pattern);
3695+
matchers.add(matcher);
3696+
}
36903697
}
3691-
this.requestMatcher = new OrRequestMatcher(createAntMatchers(patterns));
3698+
this.requestMatcher = new OrRequestMatcher(matchers);
36923699
return this;
36933700
}
36943701

@@ -3717,15 +3724,11 @@ public HttpSecurity webAuthn(Customizer<WebAuthnConfigurer<HttpSecurity>> webAut
37173724
return HttpSecurity.this;
37183725
}
37193726

3720-
private List<RequestMatcher> createAntMatchers(String... patterns) {
3721-
List<RequestMatcher> matchers = new ArrayList<>(patterns.length);
3722-
for (String pattern : patterns) {
3723-
matchers.add(new AntPathRequestMatcher(pattern));
3724-
}
3725-
return matchers;
3727+
private RequestMatcher createAntMatcher(String pattern) {
3728+
return new AntPathRequestMatcher(pattern);
37263729
}
37273730

3728-
private List<RequestMatcher> createMvcMatchers(String... mvcPatterns) {
3731+
private RequestMatcher createMvcMatcher(String mvcPattern) {
37293732
ResolvableType type = ResolvableType.forClassWithGenerics(ObjectPostProcessor.class, Object.class);
37303733
ObjectProvider<ObjectPostProcessor<Object>> postProcessors = getContext().getBeanProvider(type);
37313734
ObjectPostProcessor<Object> opp = postProcessors.getObject();
@@ -3736,13 +3739,9 @@ private List<RequestMatcher> createMvcMatchers(String... mvcPatterns) {
37363739
}
37373740
HandlerMappingIntrospector introspector = getContext().getBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME,
37383741
HandlerMappingIntrospector.class);
3739-
List<RequestMatcher> matchers = new ArrayList<>(mvcPatterns.length);
3740-
for (String mvcPattern : mvcPatterns) {
3741-
MvcRequestMatcher matcher = new MvcRequestMatcher(introspector, mvcPattern);
3742-
opp.postProcess(matcher);
3743-
matchers.add(matcher);
3744-
}
3745-
return matchers;
3742+
MvcRequestMatcher matcher = new MvcRequestMatcher(introspector, mvcPattern);
3743+
opp.postProcess(matcher);
3744+
return matcher;
37463745
}
37473746

37483747
/**

config/src/main/java/org/springframework/security/config/annotation/web/configuration/HttpSecurityConfiguration.java

+2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer;
3636
import org.springframework.security.config.annotation.authentication.configurers.provisioning.JdbcUserDetailsManagerConfigurer;
3737
import org.springframework.security.config.annotation.authentication.configurers.userdetails.DaoAuthenticationConfigurer;
38+
import org.springframework.security.config.annotation.web.RequestMatcherFactory;
3839
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
3940
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
4041
import org.springframework.security.config.annotation.web.configurers.DefaultLoginPageConfigurer;
@@ -104,6 +105,7 @@ void setContentNegotiationStrategy(ContentNegotiationStrategy contentNegotiation
104105
@Bean(HTTPSECURITY_BEAN_NAME)
105106
@Scope("prototype")
106107
HttpSecurity httpSecurity() throws Exception {
108+
RequestMatcherFactory.setApplicationContext(this.context);
107109
LazyPasswordEncoder passwordEncoder = new LazyPasswordEncoder(this.context);
108110
AuthenticationManagerBuilder authenticationBuilder = new DefaultPasswordEncoderAuthenticationManagerBuilder(
109111
this.objectPostProcessor, passwordEncoder);

config/src/main/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurer.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616

1717
package org.springframework.security.config.annotation.web.configurers;
1818

19+
import org.springframework.http.HttpMethod;
1920
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
21+
import org.springframework.security.config.annotation.web.RequestMatcherFactory;
2022
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
2123
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
2224
import org.springframework.security.web.AuthenticationEntryPoint;
@@ -26,7 +28,6 @@
2628
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
2729
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
2830
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
29-
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
3031
import org.springframework.security.web.util.matcher.RequestMatcher;
3132

3233
/**
@@ -234,7 +235,7 @@ public void init(H http) throws Exception {
234235

235236
@Override
236237
protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {
237-
return new AntPathRequestMatcher(loginProcessingUrl, "POST");
238+
return RequestMatcherFactory.matcher(HttpMethod.POST, loginProcessingUrl);
238239
}
239240

240241
/**

config/src/main/java/org/springframework/security/config/annotation/web/configurers/LogoutConfigurer.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@
2222

2323
import jakarta.servlet.http.HttpSession;
2424

25+
import org.springframework.http.HttpMethod;
2526
import org.springframework.security.config.annotation.SecurityConfigurer;
2627
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
28+
import org.springframework.security.config.annotation.web.RequestMatcherFactory;
2729
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
2830
import org.springframework.security.core.Authentication;
2931
import org.springframework.security.web.authentication.logout.CookieClearingLogoutHandler;
@@ -37,7 +39,6 @@
3739
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
3840
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
3941
import org.springframework.security.web.context.SecurityContextRepository;
40-
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
4142
import org.springframework.security.web.util.matcher.OrRequestMatcher;
4243
import org.springframework.security.web.util.matcher.RequestMatcher;
4344
import org.springframework.util.Assert;
@@ -368,7 +369,7 @@ private RequestMatcher createLogoutRequestMatcher(H http) {
368369
}
369370

370371
private RequestMatcher createLogoutRequestMatcher(String httpMethod) {
371-
return new AntPathRequestMatcher(this.logoutUrl, httpMethod);
372+
return RequestMatcherFactory.matcher(HttpMethod.valueOf(httpMethod), this.logoutUrl);
372373
}
373374

374375
}

config/src/main/java/org/springframework/security/config/annotation/web/configurers/PasswordManagementConfigurer.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
package org.springframework.security.config.annotation.web.configurers;
1818

1919
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
20+
import org.springframework.security.config.annotation.web.RequestMatcherFactory;
2021
import org.springframework.security.web.RequestMatcherRedirectFilter;
2122
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
22-
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
2323
import org.springframework.util.Assert;
2424

2525
/**
@@ -55,7 +55,7 @@ public PasswordManagementConfigurer<B> changePasswordPage(String changePasswordP
5555
@Override
5656
public void configure(B http) throws Exception {
5757
RequestMatcherRedirectFilter changePasswordFilter = new RequestMatcherRedirectFilter(
58-
new AntPathRequestMatcher(WELL_KNOWN_CHANGE_PASSWORD_PATTERN), this.changePasswordPage);
58+
RequestMatcherFactory.matcher(WELL_KNOWN_CHANGE_PASSWORD_PATTERN), this.changePasswordPage);
5959
http.addFilterBefore(postProcess(changePasswordFilter), UsernamePasswordAuthenticationFilter.class);
6060
}
6161

config/src/main/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurer.java

+13-2
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@
2121
import java.util.List;
2222

2323
import org.springframework.context.ApplicationContext;
24+
import org.springframework.http.HttpMethod;
2425
import org.springframework.http.MediaType;
2526
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
27+
import org.springframework.security.config.annotation.web.RequestMatcherFactory;
2628
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
2729
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
2830
import org.springframework.security.web.savedrequest.NullRequestCache;
@@ -140,13 +142,13 @@ private <T> T getBeanOrNull(Class<T> type) {
140142

141143
@SuppressWarnings("unchecked")
142144
private RequestMatcher createDefaultSavedRequestMatcher(H http) {
143-
RequestMatcher notFavIcon = new NegatedRequestMatcher(new AntPathRequestMatcher("/**/favicon.*"));
145+
RequestMatcher notFavIcon = new NegatedRequestMatcher(getFaviconRequestMatcher());
144146
RequestMatcher notXRequestedWith = new NegatedRequestMatcher(
145147
new RequestHeaderRequestMatcher("X-Requested-With", "XMLHttpRequest"));
146148
boolean isCsrfEnabled = http.getConfigurer(CsrfConfigurer.class) != null;
147149
List<RequestMatcher> matchers = new ArrayList<>();
148150
if (isCsrfEnabled) {
149-
RequestMatcher getRequests = new AntPathRequestMatcher("/**", "GET");
151+
RequestMatcher getRequests = RequestMatcherFactory.matcher(HttpMethod.GET, "/**");
150152
matchers.add(0, getRequests);
151153
}
152154
matchers.add(notFavIcon);
@@ -167,4 +169,13 @@ private RequestMatcher notMatchingMediaType(H http, MediaType mediaType) {
167169
return new NegatedRequestMatcher(mediaRequest);
168170
}
169171

172+
private RequestMatcher getFaviconRequestMatcher() {
173+
if (RequestMatcherFactory.usesPathPatterns()) {
174+
return RequestMatcherFactory.matcher("/favicon.*");
175+
}
176+
else {
177+
return new AntPathRequestMatcher("/**/favicon.*");
178+
}
179+
}
180+
170181
}

config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.springframework.security.authentication.AuthenticationProvider;
3939
import org.springframework.security.config.Customizer;
4040
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
41+
import org.springframework.security.config.annotation.web.RequestMatcherFactory;
4142
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
4243
import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
4344
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
@@ -91,7 +92,6 @@
9192
import org.springframework.security.web.csrf.CsrfToken;
9293
import org.springframework.security.web.savedrequest.RequestCache;
9394
import org.springframework.security.web.util.matcher.AndRequestMatcher;
94-
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
9595
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
9696
import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
9797
import org.springframework.security.web.util.matcher.OrRequestMatcher;
@@ -431,7 +431,7 @@ public void configure(B http) throws Exception {
431431

432432
@Override
433433
protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {
434-
return new AntPathRequestMatcher(loginProcessingUrl);
434+
return RequestMatcherFactory.matcher(loginProcessingUrl);
435435
}
436436

437437
private OAuth2AuthorizationRequestResolver getAuthorizationRequestResolver() {
@@ -569,8 +569,8 @@ private Map<String, String> getLoginLinks() {
569569
}
570570

571571
private AuthenticationEntryPoint getLoginEntryPoint(B http, String providerLoginPage) {
572-
RequestMatcher loginPageMatcher = new AntPathRequestMatcher(this.getLoginPage());
573-
RequestMatcher faviconMatcher = new AntPathRequestMatcher("/favicon.ico");
572+
RequestMatcher loginPageMatcher = RequestMatcherFactory.matcher(this.getLoginPage());
573+
RequestMatcher faviconMatcher = RequestMatcherFactory.matcher("/favicon.ico");
574574
RequestMatcher defaultEntryPointMatcher = this.getAuthenticationEntryPointMatcher(http);
575575
RequestMatcher defaultLoginPageMatcher = new AndRequestMatcher(
576576
new OrRequestMatcher(loginPageMatcher, faviconMatcher), defaultEntryPointMatcher);

config/src/main/java/org/springframework/security/config/annotation/web/configurers/ott/OneTimeTokenLoginConfigurer.java

+4-5
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.springframework.security.authentication.ott.OneTimeTokenService;
3232
import org.springframework.security.config.Customizer;
3333
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
34+
import org.springframework.security.config.annotation.web.RequestMatcherFactory;
3435
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
3536
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
3637
import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
@@ -56,8 +57,6 @@
5657
import org.springframework.util.Assert;
5758
import org.springframework.util.StringUtils;
5859

59-
import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher;
60-
6160
/**
6261
* An {@link AbstractHttpConfigurer} for One-Time Token Login.
6362
*
@@ -163,7 +162,7 @@ public void configure(H http) throws Exception {
163162
private void configureOttGenerateFilter(H http) {
164163
GenerateOneTimeTokenFilter generateFilter = new GenerateOneTimeTokenFilter(getOneTimeTokenService(),
165164
getOneTimeTokenGenerationSuccessHandler());
166-
generateFilter.setRequestMatcher(antMatcher(HttpMethod.POST, this.tokenGeneratingUrl));
165+
generateFilter.setRequestMatcher(RequestMatcherFactory.matcher(HttpMethod.POST, this.tokenGeneratingUrl));
167166
generateFilter.setRequestResolver(getGenerateRequestResolver());
168167
http.addFilter(postProcess(generateFilter));
169168
http.addFilter(DefaultResourcesFilter.css());
@@ -190,7 +189,7 @@ private void configureSubmitPage(H http) {
190189
}
191190
DefaultOneTimeTokenSubmitPageGeneratingFilter submitPage = new DefaultOneTimeTokenSubmitPageGeneratingFilter();
192191
submitPage.setResolveHiddenInputs(this::hiddenInputs);
193-
submitPage.setRequestMatcher(antMatcher(HttpMethod.GET, this.defaultSubmitPageUrl));
192+
submitPage.setRequestMatcher(RequestMatcherFactory.matcher(HttpMethod.GET, this.defaultSubmitPageUrl));
194193
submitPage.setLoginProcessingUrl(this.getLoginProcessingUrl());
195194
http.addFilter(postProcess(submitPage));
196195
}
@@ -207,7 +206,7 @@ private AuthenticationProvider getAuthenticationProvider() {
207206

208207
@Override
209208
protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {
210-
return antMatcher(HttpMethod.POST, loginProcessingUrl);
209+
return RequestMatcherFactory.matcher(HttpMethod.POST, loginProcessingUrl);
211210
}
212211

213212
/**

0 commit comments

Comments
 (0)