Skip to content

Commit

Permalink
Add Opt-in PathPattern Strategy
Browse files Browse the repository at this point in the history
  • Loading branch information
jzheaux committed Feb 21, 2025
1 parent 588220a commit 7d301f8
Show file tree
Hide file tree
Showing 18 changed files with 589 additions and 69 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 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.
Expand Down Expand Up @@ -219,9 +219,14 @@ public C requestMatchers(HttpMethod method, String... patterns) {
}
List<RequestMatcher> matchers = new ArrayList<>();
for (String pattern : patterns) {
AntPathRequestMatcher ant = new AntPathRequestMatcher(pattern, (method != null) ? method.name() : null);
MvcRequestMatcher mvc = createMvcMatchers(method, pattern).get(0);
matchers.add(new DeferredRequestMatcher((c) -> resolve(ant, mvc, c), mvc, ant));
if (RequestMatcherFactory.usesPathPatterns()) {
matchers.add(RequestMatcherFactory.matcher(method, pattern));
}
else {
AntPathRequestMatcher ant = new AntPathRequestMatcher(pattern, (method != null) ? method.name() : null);
MvcRequestMatcher mvc = createMvcMatchers(method, pattern).get(0);
matchers.add(new DeferredRequestMatcher((c) -> resolve(ant, mvc, c), mvc, ant));
}
}
return requestMatchers(matchers.toArray(new RequestMatcher[0]));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright 2002-2025 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;

import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpMethod;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;

/**
* This utility exists only to facilitate applications opting into using path patterns in
* the HttpSecurity DSL. It is for internal use only.
*
* @deprecated
*/
@Deprecated(forRemoval = true)
public final class RequestMatcherFactory {

private static PathPatternRequestMatcher.Builder builder;

public static void setApplicationContext(ApplicationContext context) {
builder = context.getBeanProvider(PathPatternRequestMatcher.Builder.class).getIfUnique();
}

public static boolean usesPathPatterns() {
return builder != null;
}

public static RequestMatcher matcher(String path) {
return matcher(null, path);
}

public static RequestMatcher matcher(HttpMethod method, String path) {
if (builder != null) {
return builder.matcher(method, path);
}
return new AntPathRequestMatcher(path, (method != null) ? method.name() : null);
}

private RequestMatcherFactory() {

}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 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.
Expand Down Expand Up @@ -45,6 +45,7 @@
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.RequestMatcherFactory;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
import org.springframework.security.config.annotation.web.configurers.AnonymousConfigurer;
Expand Down Expand Up @@ -3684,11 +3685,17 @@ public HttpSecurity securityMatcher(RequestMatcher requestMatcher) {
* @see MvcRequestMatcher
*/
public HttpSecurity securityMatcher(String... patterns) {
if (mvcPresent) {
this.requestMatcher = new OrRequestMatcher(createMvcMatchers(patterns));
return this;
List<RequestMatcher> matchers = new ArrayList<>();
for (String pattern : patterns) {
if (RequestMatcherFactory.usesPathPatterns()) {
matchers.add(RequestMatcherFactory.matcher(pattern));
}
else {
RequestMatcher matcher = mvcPresent ? createMvcMatcher(pattern) : createAntMatcher(pattern);
matchers.add(matcher);
}
}
this.requestMatcher = new OrRequestMatcher(createAntMatchers(patterns));
this.requestMatcher = new OrRequestMatcher(matchers);
return this;
}

Expand Down Expand Up @@ -3717,15 +3724,11 @@ public HttpSecurity webAuthn(Customizer<WebAuthnConfigurer<HttpSecurity>> webAut
return HttpSecurity.this;
}

private List<RequestMatcher> createAntMatchers(String... patterns) {
List<RequestMatcher> matchers = new ArrayList<>(patterns.length);
for (String pattern : patterns) {
matchers.add(new AntPathRequestMatcher(pattern));
}
return matchers;
private RequestMatcher createAntMatcher(String pattern) {
return new AntPathRequestMatcher(pattern);
}

private List<RequestMatcher> createMvcMatchers(String... mvcPatterns) {
private RequestMatcher createMvcMatcher(String mvcPattern) {
ResolvableType type = ResolvableType.forClassWithGenerics(ObjectPostProcessor.class, Object.class);
ObjectProvider<ObjectPostProcessor<Object>> postProcessors = getContext().getBeanProvider(type);
ObjectPostProcessor<Object> opp = postProcessors.getObject();
Expand All @@ -3736,13 +3739,9 @@ private List<RequestMatcher> createMvcMatchers(String... mvcPatterns) {
}
HandlerMappingIntrospector introspector = getContext().getBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME,
HandlerMappingIntrospector.class);
List<RequestMatcher> matchers = new ArrayList<>(mvcPatterns.length);
for (String mvcPattern : mvcPatterns) {
MvcRequestMatcher matcher = new MvcRequestMatcher(introspector, mvcPattern);
opp.postProcess(matcher);
matchers.add(matcher);
}
return matchers;
MvcRequestMatcher matcher = new MvcRequestMatcher(introspector, mvcPattern);
opp.postProcess(matcher);
return matcher;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer;
import org.springframework.security.config.annotation.authentication.configurers.provisioning.JdbcUserDetailsManagerConfigurer;
import org.springframework.security.config.annotation.authentication.configurers.userdetails.DaoAuthenticationConfigurer;
import org.springframework.security.config.annotation.web.RequestMatcherFactory;
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;
Expand Down Expand Up @@ -104,6 +105,7 @@ void setContentNegotiationStrategy(ContentNegotiationStrategy contentNegotiation
@Bean(HTTPSECURITY_BEAN_NAME)
@Scope("prototype")
HttpSecurity httpSecurity() throws Exception {
RequestMatcherFactory.setApplicationContext(this.context);
LazyPasswordEncoder passwordEncoder = new LazyPasswordEncoder(this.context);
AuthenticationManagerBuilder authenticationBuilder = new DefaultPasswordEncoderAuthenticationManagerBuilder(
this.objectPostProcessor, passwordEncoder);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@

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

import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.RequestMatcherFactory;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.AuthenticationEntryPoint;
Expand All @@ -26,7 +28,6 @@
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;

/**
Expand Down Expand Up @@ -234,7 +235,7 @@ public void init(H http) throws Exception {

@Override
protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {
return new AntPathRequestMatcher(loginProcessingUrl, "POST");
return RequestMatcherFactory.matcher(HttpMethod.POST, loginProcessingUrl);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@

import jakarta.servlet.http.HttpSession;

import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.SecurityConfigurer;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.RequestMatcherFactory;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.CookieClearingLogoutHandler;
Expand All @@ -37,7 +39,6 @@
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
Expand Down Expand Up @@ -368,7 +369,7 @@ private RequestMatcher createLogoutRequestMatcher(H http) {
}

private RequestMatcher createLogoutRequestMatcher(String httpMethod) {
return new AntPathRequestMatcher(this.logoutUrl, httpMethod);
return RequestMatcherFactory.matcher(HttpMethod.valueOf(httpMethod), this.logoutUrl);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
package org.springframework.security.config.annotation.web.configurers;

import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.RequestMatcherFactory;
import org.springframework.security.web.RequestMatcherRedirectFilter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.Assert;

/**
Expand Down Expand Up @@ -55,7 +55,7 @@ public PasswordManagementConfigurer<B> changePasswordPage(String changePasswordP
@Override
public void configure(B http) throws Exception {
RequestMatcherRedirectFilter changePasswordFilter = new RequestMatcherRedirectFilter(
new AntPathRequestMatcher(WELL_KNOWN_CHANGE_PASSWORD_PATTERN), this.changePasswordPage);
RequestMatcherFactory.matcher(WELL_KNOWN_CHANGE_PASSWORD_PATTERN), this.changePasswordPage);
http.addFilterBefore(postProcess(changePasswordFilter), UsernamePasswordAuthenticationFilter.class);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@
import java.util.List;

import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.RequestMatcherFactory;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.NullRequestCache;
Expand Down Expand Up @@ -140,13 +142,13 @@ private <T> T getBeanOrNull(Class<T> type) {

@SuppressWarnings("unchecked")
private RequestMatcher createDefaultSavedRequestMatcher(H http) {
RequestMatcher notFavIcon = new NegatedRequestMatcher(new AntPathRequestMatcher("/**/favicon.*"));
RequestMatcher notFavIcon = new NegatedRequestMatcher(getFaviconRequestMatcher());
RequestMatcher notXRequestedWith = new NegatedRequestMatcher(
new RequestHeaderRequestMatcher("X-Requested-With", "XMLHttpRequest"));
boolean isCsrfEnabled = http.getConfigurer(CsrfConfigurer.class) != null;
List<RequestMatcher> matchers = new ArrayList<>();
if (isCsrfEnabled) {
RequestMatcher getRequests = new AntPathRequestMatcher("/**", "GET");
RequestMatcher getRequests = RequestMatcherFactory.matcher(HttpMethod.GET, "/**");
matchers.add(0, getRequests);
}
matchers.add(notFavIcon);
Expand All @@ -167,4 +169,13 @@ private RequestMatcher notMatchingMediaType(H http, MediaType mediaType) {
return new NegatedRequestMatcher(mediaRequest);
}

private RequestMatcher getFaviconRequestMatcher() {
if (RequestMatcherFactory.usesPathPatterns()) {
return RequestMatcherFactory.matcher("/favicon.*");
}
else {
return new AntPathRequestMatcher("/**/favicon.*");
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.RequestMatcherFactory;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
Expand Down Expand Up @@ -91,7 +92,6 @@
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.util.matcher.AndRequestMatcher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
Expand Down Expand Up @@ -431,7 +431,7 @@ public void configure(B http) throws Exception {

@Override
protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {
return new AntPathRequestMatcher(loginProcessingUrl);
return RequestMatcherFactory.matcher(loginProcessingUrl);
}

private OAuth2AuthorizationRequestResolver getAuthorizationRequestResolver() {
Expand Down Expand Up @@ -569,8 +569,8 @@ private Map<String, String> getLoginLinks() {
}

private AuthenticationEntryPoint getLoginEntryPoint(B http, String providerLoginPage) {
RequestMatcher loginPageMatcher = new AntPathRequestMatcher(this.getLoginPage());
RequestMatcher faviconMatcher = new AntPathRequestMatcher("/favicon.ico");
RequestMatcher loginPageMatcher = RequestMatcherFactory.matcher(this.getLoginPage());
RequestMatcher faviconMatcher = RequestMatcherFactory.matcher("/favicon.ico");
RequestMatcher defaultEntryPointMatcher = this.getAuthenticationEntryPointMatcher(http);
RequestMatcher defaultLoginPageMatcher = new AndRequestMatcher(
new OrRequestMatcher(loginPageMatcher, faviconMatcher), defaultEntryPointMatcher);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.springframework.security.authentication.ott.OneTimeTokenService;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.RequestMatcherFactory;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
Expand All @@ -56,8 +57,6 @@
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher;

/**
* An {@link AbstractHttpConfigurer} for One-Time Token Login.
*
Expand Down Expand Up @@ -163,7 +162,7 @@ public void configure(H http) throws Exception {
private void configureOttGenerateFilter(H http) {
GenerateOneTimeTokenFilter generateFilter = new GenerateOneTimeTokenFilter(getOneTimeTokenService(),
getOneTimeTokenGenerationSuccessHandler());
generateFilter.setRequestMatcher(antMatcher(HttpMethod.POST, this.tokenGeneratingUrl));
generateFilter.setRequestMatcher(RequestMatcherFactory.matcher(HttpMethod.POST, this.tokenGeneratingUrl));
generateFilter.setRequestResolver(getGenerateRequestResolver());
http.addFilter(postProcess(generateFilter));
http.addFilter(DefaultResourcesFilter.css());
Expand All @@ -190,7 +189,7 @@ private void configureSubmitPage(H http) {
}
DefaultOneTimeTokenSubmitPageGeneratingFilter submitPage = new DefaultOneTimeTokenSubmitPageGeneratingFilter();
submitPage.setResolveHiddenInputs(this::hiddenInputs);
submitPage.setRequestMatcher(antMatcher(HttpMethod.GET, this.defaultSubmitPageUrl));
submitPage.setRequestMatcher(RequestMatcherFactory.matcher(HttpMethod.GET, this.defaultSubmitPageUrl));
submitPage.setLoginProcessingUrl(this.getLoginProcessingUrl());
http.addFilter(postProcess(submitPage));
}
Expand All @@ -207,7 +206,7 @@ private AuthenticationProvider getAuthenticationProvider() {

@Override
protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {
return antMatcher(HttpMethod.POST, loginProcessingUrl);
return RequestMatcherFactory.matcher(HttpMethod.POST, loginProcessingUrl);
}

/**
Expand Down
Loading

0 comments on commit 7d301f8

Please sign in to comment.