Skip to content

Commit 146fd61

Browse files
committed
Add Ability to Opt-in to PathPattern
Closes gh-16573
1 parent 8441b55 commit 146fd61

File tree

19 files changed

+329
-186
lines changed

19 files changed

+329
-186
lines changed

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

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
4747
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
4848
import org.springframework.security.web.util.matcher.DispatcherTypeRequestMatcher;
49+
import org.springframework.security.web.util.matcher.MethodPatternRequestMatcherFactory;
4950
import org.springframework.security.web.util.matcher.OrRequestMatcher;
5051
import org.springframework.security.web.util.matcher.RegexRequestMatcher;
5152
import org.springframework.security.web.util.matcher.RequestMatcher;
@@ -218,10 +219,9 @@ public C requestMatchers(HttpMethod method, String... patterns) {
218219
return requestMatchers(RequestMatchers.antMatchersAsArray(method, patterns));
219220
}
220221
List<RequestMatcher> matchers = new ArrayList<>();
222+
MethodPatternRequestMatcherFactory requestMatcherFactory = getRequestMatcherFactory();
221223
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));
224+
matchers.add(requestMatcherFactory.matcher(method, pattern));
225225
}
226226
return requestMatchers(matchers.toArray(new RequestMatcher[0]));
227227
}
@@ -331,6 +331,11 @@ public C requestMatchers(HttpMethod method) {
331331
*/
332332
protected abstract C chainRequestMatchers(List<RequestMatcher> requestMatchers);
333333

334+
private MethodPatternRequestMatcherFactory getRequestMatcherFactory() {
335+
return this.context.getBeanProvider(MethodPatternRequestMatcherFactory.class)
336+
.getIfUnique(DefaultMethodPatternRequestMatcherFactory::new);
337+
}
338+
334339
/**
335340
* Utilities for creating {@link RequestMatcher} instances.
336341
*
@@ -404,6 +409,17 @@ static List<RequestMatcher> regexMatchers(String... regexPatterns) {
404409

405410
}
406411

412+
class DefaultMethodPatternRequestMatcherFactory implements MethodPatternRequestMatcherFactory {
413+
414+
@Override
415+
public RequestMatcher matcher(HttpMethod method, String pattern) {
416+
AntPathRequestMatcher ant = new AntPathRequestMatcher(pattern, (method != null) ? method.name() : null);
417+
MvcRequestMatcher mvc = createMvcMatchers(method, pattern).get(0);
418+
return new DeferredRequestMatcher((c) -> resolve(ant, mvc, c), mvc, ant);
419+
}
420+
421+
}
422+
407423
static class DeferredRequestMatcher implements RequestMatcher {
408424

409425
final Function<ServletContext, RequestMatcher> requestMatcherFactory;

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

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
import org.springframework.security.web.session.HttpSessionEventPublisher;
9494
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
9595
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
96+
import org.springframework.security.web.util.matcher.MethodPatternRequestMatcherFactory;
9697
import org.springframework.security.web.util.matcher.OrRequestMatcher;
9798
import org.springframework.security.web.util.matcher.RequestMatcher;
9899
import org.springframework.util.Assert;
@@ -3684,11 +3685,14 @@ 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+
MethodPatternRequestMatcherFactory factory = getSharedObject(ApplicationContext.class)
3690+
.getBeanProvider(MethodPatternRequestMatcherFactory.class)
3691+
.getIfUnique(() -> (method, pattern) -> mvcPresent ? createMvcMatcher(pattern) : createAntMatcher(pattern));
3692+
for (String pattern : patterns) {
3693+
matchers.add(factory.matcher(pattern));
36903694
}
3691-
this.requestMatcher = new OrRequestMatcher(createAntMatchers(patterns));
3695+
this.requestMatcher = new OrRequestMatcher(matchers);
36923696
return this;
36933697
}
36943698

@@ -3717,15 +3721,11 @@ public HttpSecurity webAuthn(Customizer<WebAuthnConfigurer<HttpSecurity>> webAut
37173721
return HttpSecurity.this;
37183722
}
37193723

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;
3724+
private RequestMatcher createAntMatcher(String pattern) {
3725+
return new AntPathRequestMatcher(pattern);
37263726
}
37273727

3728-
private List<RequestMatcher> createMvcMatchers(String... mvcPatterns) {
3728+
private RequestMatcher createMvcMatcher(String mvcPattern) {
37293729
ResolvableType type = ResolvableType.forClassWithGenerics(ObjectPostProcessor.class, Object.class);
37303730
ObjectProvider<ObjectPostProcessor<Object>> postProcessors = getContext().getBeanProvider(type);
37313731
ObjectPostProcessor<Object> opp = postProcessors.getObject();
@@ -3736,13 +3736,9 @@ private List<RequestMatcher> createMvcMatchers(String... mvcPatterns) {
37363736
}
37373737
HandlerMappingIntrospector introspector = getContext().getBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME,
37383738
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;
3739+
MvcRequestMatcher matcher = new MvcRequestMatcher(introspector, mvcPattern);
3740+
opp.postProcess(matcher);
3741+
return matcher;
37463742
}
37473743

37483744
/**

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

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

19+
import org.springframework.context.ApplicationContext;
20+
import org.springframework.http.HttpMethod;
1921
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
2022
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
2123
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -27,6 +29,7 @@
2729
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
2830
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
2931
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
32+
import org.springframework.security.web.util.matcher.MethodPatternRequestMatcherFactory;
3033
import org.springframework.security.web.util.matcher.RequestMatcher;
3134

3235
/**
@@ -234,7 +237,7 @@ public void init(H http) throws Exception {
234237

235238
@Override
236239
protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {
237-
return new AntPathRequestMatcher(loginProcessingUrl, "POST");
240+
return getRequestMatcherFactory().matcher(HttpMethod.POST, loginProcessingUrl);
238241
}
239242

240243
/**
@@ -271,4 +274,10 @@ private void initDefaultLoginFilter(H http) {
271274
}
272275
}
273276

277+
private MethodPatternRequestMatcherFactory getRequestMatcherFactory() {
278+
return getBuilder().getSharedObject(ApplicationContext.class)
279+
.getBeanProvider(MethodPatternRequestMatcherFactory.class)
280+
.getIfUnique(() -> AntPathRequestMatcher::antMatcher);
281+
}
282+
274283
}

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222

2323
import jakarta.servlet.http.HttpSession;
2424

25+
import org.springframework.context.ApplicationContext;
26+
import org.springframework.http.HttpMethod;
2527
import org.springframework.security.config.annotation.SecurityConfigurer;
2628
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
2729
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@@ -38,6 +40,7 @@
3840
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
3941
import org.springframework.security.web.context.SecurityContextRepository;
4042
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
43+
import org.springframework.security.web.util.matcher.MethodPatternRequestMatcherFactory;
4144
import org.springframework.security.web.util.matcher.OrRequestMatcher;
4245
import org.springframework.security.web.util.matcher.RequestMatcher;
4346
import org.springframework.util.Assert;
@@ -368,7 +371,13 @@ private RequestMatcher createLogoutRequestMatcher(H http) {
368371
}
369372

370373
private RequestMatcher createLogoutRequestMatcher(String httpMethod) {
371-
return new AntPathRequestMatcher(this.logoutUrl, httpMethod);
374+
return getRequestMatcherFactory().matcher(HttpMethod.valueOf(httpMethod), this.logoutUrl);
375+
}
376+
377+
private MethodPatternRequestMatcherFactory getRequestMatcherFactory() {
378+
return getBuilder().getSharedObject(ApplicationContext.class)
379+
.getBeanProvider(MethodPatternRequestMatcherFactory.class)
380+
.getIfUnique(() -> AntPathRequestMatcher::antMatcher);
372381
}
373382

374383
}

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@
1616

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

19+
import org.springframework.context.ApplicationContext;
1920
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
2021
import org.springframework.security.web.RequestMatcherRedirectFilter;
2122
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
2223
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
24+
import org.springframework.security.web.util.matcher.MethodPatternRequestMatcherFactory;
2325
import org.springframework.util.Assert;
2426

2527
/**
@@ -55,8 +57,14 @@ public PasswordManagementConfigurer<B> changePasswordPage(String changePasswordP
5557
@Override
5658
public void configure(B http) throws Exception {
5759
RequestMatcherRedirectFilter changePasswordFilter = new RequestMatcherRedirectFilter(
58-
new AntPathRequestMatcher(WELL_KNOWN_CHANGE_PASSWORD_PATTERN), this.changePasswordPage);
60+
getRequestMatcherFactory().matcher(WELL_KNOWN_CHANGE_PASSWORD_PATTERN), this.changePasswordPage);
5961
http.addFilterBefore(postProcess(changePasswordFilter), UsernamePasswordAuthenticationFilter.class);
6062
}
6163

64+
private MethodPatternRequestMatcherFactory getRequestMatcherFactory() {
65+
return getBuilder().getSharedObject(ApplicationContext.class)
66+
.getBeanProvider(MethodPatternRequestMatcherFactory.class)
67+
.getIfUnique(() -> AntPathRequestMatcher::antMatcher);
68+
}
69+
6270
}

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
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;
2627
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@@ -31,6 +32,7 @@
3132
import org.springframework.security.web.util.matcher.AndRequestMatcher;
3233
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
3334
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
35+
import org.springframework.security.web.util.matcher.MethodPatternRequestMatcherFactory;
3436
import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
3537
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
3638
import org.springframework.security.web.util.matcher.RequestMatcher;
@@ -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(getRequestMatcherFactory().matcher("/**/favicon.*"));
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 = getRequestMatcherFactory().matcher(HttpMethod.GET, "/**");
150152
matchers.add(0, getRequests);
151153
}
152154
matchers.add(notFavIcon);
@@ -167,4 +169,10 @@ private RequestMatcher notMatchingMediaType(H http, MediaType mediaType) {
167169
return new NegatedRequestMatcher(mediaRequest);
168170
}
169171

172+
private MethodPatternRequestMatcherFactory getRequestMatcherFactory() {
173+
return getBuilder().getSharedObject(ApplicationContext.class)
174+
.getBeanProvider(MethodPatternRequestMatcherFactory.class)
175+
.getIfUnique(() -> AntPathRequestMatcher::antMatcher);
176+
}
177+
170178
}

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
import org.springframework.security.web.util.matcher.AndRequestMatcher;
9494
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
9595
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
96+
import org.springframework.security.web.util.matcher.MethodPatternRequestMatcherFactory;
9697
import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
9798
import org.springframework.security.web.util.matcher.OrRequestMatcher;
9899
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
@@ -431,7 +432,7 @@ public void configure(B http) throws Exception {
431432

432433
@Override
433434
protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {
434-
return new AntPathRequestMatcher(loginProcessingUrl);
435+
return getRequestMatcherFactory().matcher(loginProcessingUrl);
435436
}
436437

437438
private OAuth2AuthorizationRequestResolver getAuthorizationRequestResolver() {
@@ -569,8 +570,8 @@ private Map<String, String> getLoginLinks() {
569570
}
570571

571572
private AuthenticationEntryPoint getLoginEntryPoint(B http, String providerLoginPage) {
572-
RequestMatcher loginPageMatcher = new AntPathRequestMatcher(this.getLoginPage());
573-
RequestMatcher faviconMatcher = new AntPathRequestMatcher("/favicon.ico");
573+
RequestMatcher loginPageMatcher = getRequestMatcherFactory().matcher(this.getLoginPage());
574+
RequestMatcher faviconMatcher = getRequestMatcherFactory().matcher("/favicon.ico");
574575
RequestMatcher defaultEntryPointMatcher = this.getAuthenticationEntryPointMatcher(http);
575576
RequestMatcher defaultLoginPageMatcher = new AndRequestMatcher(
576577
new OrRequestMatcher(loginPageMatcher, faviconMatcher), defaultEntryPointMatcher);
@@ -625,6 +626,12 @@ private void registerDelegateApplicationListener(ApplicationListener<?> delegate
625626
delegating.addListener(smartListener);
626627
}
627628

629+
private MethodPatternRequestMatcherFactory getRequestMatcherFactory() {
630+
return getBuilder().getSharedObject(ApplicationContext.class)
631+
.getBeanProvider(MethodPatternRequestMatcherFactory.class)
632+
.getIfUnique(() -> AntPathRequestMatcher::antMatcher);
633+
}
634+
628635
/**
629636
* Configuration options for the Authorization Server's Authorization Endpoint.
630637
*/

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,11 @@
5353
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
5454
import org.springframework.security.web.context.SecurityContextRepository;
5555
import org.springframework.security.web.csrf.CsrfToken;
56+
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
57+
import org.springframework.security.web.util.matcher.MethodPatternRequestMatcherFactory;
5658
import org.springframework.util.Assert;
5759
import org.springframework.util.StringUtils;
5860

59-
import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher;
60-
6161
public final class OneTimeTokenLoginConfigurer<H extends HttpSecurityBuilder<H>>
6262
extends AbstractHttpConfigurer<OneTimeTokenLoginConfigurer<H>, H> {
6363

@@ -123,7 +123,8 @@ private void configureOttAuthenticationFilter(H http) {
123123
AuthenticationFilter oneTimeTokenAuthenticationFilter = new AuthenticationFilter(authenticationManager,
124124
this.authenticationConverter);
125125
oneTimeTokenAuthenticationFilter.setSecurityContextRepository(getSecurityContextRepository(http));
126-
oneTimeTokenAuthenticationFilter.setRequestMatcher(antMatcher(HttpMethod.POST, this.loginProcessingUrl));
126+
oneTimeTokenAuthenticationFilter
127+
.setRequestMatcher(getRequestMatcherFactory().matcher(HttpMethod.POST, this.loginProcessingUrl));
127128
oneTimeTokenAuthenticationFilter.setFailureHandler(getAuthenticationFailureHandler());
128129
oneTimeTokenAuthenticationFilter.setSuccessHandler(this.authenticationSuccessHandler);
129130
http.addFilter(postProcess(oneTimeTokenAuthenticationFilter));
@@ -140,7 +141,7 @@ private SecurityContextRepository getSecurityContextRepository(H http) {
140141
private void configureOttGenerateFilter(H http) {
141142
GenerateOneTimeTokenFilter generateFilter = new GenerateOneTimeTokenFilter(getOneTimeTokenService(http),
142143
getOneTimeTokenGenerationSuccessHandler(http));
143-
generateFilter.setRequestMatcher(antMatcher(HttpMethod.POST, this.tokenGeneratingUrl));
144+
generateFilter.setRequestMatcher(getRequestMatcherFactory().matcher(HttpMethod.POST, this.tokenGeneratingUrl));
144145
generateFilter.setRequestResolver(getGenerateRequestResolver(http));
145146
http.addFilter(postProcess(generateFilter));
146147
http.addFilter(DefaultResourcesFilter.css());
@@ -165,7 +166,7 @@ private void configureSubmitPage(H http) {
165166
}
166167
DefaultOneTimeTokenSubmitPageGeneratingFilter submitPage = new DefaultOneTimeTokenSubmitPageGeneratingFilter();
167168
submitPage.setResolveHiddenInputs(this::hiddenInputs);
168-
submitPage.setRequestMatcher(antMatcher(HttpMethod.GET, this.defaultSubmitPageUrl));
169+
submitPage.setRequestMatcher(getRequestMatcherFactory().matcher(HttpMethod.GET, this.defaultSubmitPageUrl));
169170
submitPage.setLoginProcessingUrl(this.loginProcessingUrl);
170171
http.addFilter(postProcess(submitPage));
171172
}
@@ -180,6 +181,12 @@ private AuthenticationProvider getAuthenticationProvider(H http) {
180181
return this.authenticationProvider;
181182
}
182183

184+
private MethodPatternRequestMatcherFactory getRequestMatcherFactory() {
185+
return getBuilder().getSharedObject(ApplicationContext.class)
186+
.getBeanProvider(MethodPatternRequestMatcherFactory.class)
187+
.getIfUnique(() -> AntPathRequestMatcher::antMatcher);
188+
}
189+
183190
/**
184191
* Specifies the {@link AuthenticationProvider} to use when authenticating the user.
185192
* @param authenticationProvider

0 commit comments

Comments
 (0)