Favor PathPatternRequestMatcher

Issue gh-16828
This commit is contained in:
Josh Cummings 2025-03-26 14:33:46 -06:00
parent e65e32bb42
commit d2d9da0a39
No known key found for this signature in database
GPG Key ID: 869B37A20E876129

View File

@ -19,7 +19,6 @@ package org.springframework.security.config.http;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -27,11 +26,13 @@ import org.w3c.dom.Element;
import org.springframework.beans.BeanMetadataElement; import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanReference; import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition; import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext; import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
@ -57,7 +58,6 @@ import org.springframework.security.web.authentication.DelegatingAuthenticationE
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter; import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
import org.springframework.security.web.util.matcher.AndRequestMatcher; import org.springframework.security.web.util.matcher.AndRequestMatcher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher; import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
import org.springframework.security.web.util.matcher.NegatedRequestMatcher; import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher; import org.springframework.security.web.util.matcher.OrRequestMatcher;
@ -235,7 +235,7 @@ final class OAuth2LoginBeanDefinitionParser implements BeanDefinitionParser {
.getBeanDefinition(); .getBeanDefinition();
} }
else { else {
Map<RequestMatcher, AuthenticationEntryPoint> entryPoint = getLoginEntryPoint(element); Map<BeanDefinition, AuthenticationEntryPoint> entryPoint = getLoginEntryPoint(element);
if (entryPoint != null) { if (entryPoint != null) {
this.oauth2LoginAuthenticationEntryPoint = BeanDefinitionBuilder this.oauth2LoginAuthenticationEntryPoint = BeanDefinitionBuilder
.rootBeanDefinition(DelegatingAuthenticationEntryPoint.class) .rootBeanDefinition(DelegatingAuthenticationEntryPoint.class)
@ -364,42 +364,35 @@ final class OAuth2LoginBeanDefinitionParser implements BeanDefinitionParser {
return this.oauth2LoginLinks; return this.oauth2LoginLinks;
} }
private Map<RequestMatcher, AuthenticationEntryPoint> getLoginEntryPoint(Element element) { private Map<BeanDefinition, AuthenticationEntryPoint> getLoginEntryPoint(Element element) {
Map<RequestMatcher, AuthenticationEntryPoint> entryPoints = null; Map<BeanDefinition, AuthenticationEntryPoint> entryPoints = null;
Element clientRegsElt = DomUtils.getChildElementByTagName(element.getOwnerDocument().getDocumentElement(), Element clientRegsElt = DomUtils.getChildElementByTagName(element.getOwnerDocument().getDocumentElement(),
Elements.CLIENT_REGISTRATIONS); Elements.CLIENT_REGISTRATIONS);
if (clientRegsElt != null) { if (clientRegsElt != null) {
List<Element> clientRegList = DomUtils.getChildElementsByTagName(clientRegsElt, ELT_CLIENT_REGISTRATION); List<Element> clientRegList = DomUtils.getChildElementsByTagName(clientRegsElt, ELT_CLIENT_REGISTRATION);
if (clientRegList.size() == 1) { if (clientRegList.size() == 1) {
RequestMatcher loginPageMatcher = new AntPathRequestMatcher(DEFAULT_LOGIN_URI); BeanDefinition loginPageMatcher = BeanDefinitionBuilder
RequestMatcher faviconMatcher = new AntPathRequestMatcher("/favicon.ico"); .rootBeanDefinition(RequestMatcherFactoryBean.class)
RequestMatcher defaultEntryPointMatcher = this.getAuthenticationEntryPointMatcher(); .addConstructorArgValue(DEFAULT_LOGIN_URI)
RequestMatcher defaultLoginPageMatcher = new AndRequestMatcher( .getBeanDefinition();
new OrRequestMatcher(loginPageMatcher, faviconMatcher), defaultEntryPointMatcher); BeanDefinition faviconMatcher = BeanDefinitionBuilder
RequestMatcher notXRequestedWith = new NegatedRequestMatcher( .rootBeanDefinition(RequestMatcherFactoryBean.class)
new RequestHeaderRequestMatcher("X-Requested-With", "XMLHttpRequest")); .addConstructorArgValue("/favicon.ico")
.getBeanDefinition();
BeanDefinition entryPointMatcher = BeanDefinitionBuilder
.rootBeanDefinition(EntryPointMatcherFactoryBean.class)
.addConstructorArgValue(loginPageMatcher)
.addConstructorArgValue(faviconMatcher)
.getBeanDefinition();
Element clientRegElt = clientRegList.get(0); Element clientRegElt = clientRegList.get(0);
entryPoints = new LinkedHashMap<>(); entryPoints = new ManagedMap<>();
entryPoints.put( entryPoints.put(entryPointMatcher, new LoginUrlAuthenticationEntryPoint(
new AndRequestMatcher(notXRequestedWith, new NegatedRequestMatcher(defaultLoginPageMatcher)), DEFAULT_AUTHORIZATION_REQUEST_BASE_URI + "/" + clientRegElt.getAttribute(ATT_REGISTRATION_ID)));
new LoginUrlAuthenticationEntryPoint(DEFAULT_AUTHORIZATION_REQUEST_BASE_URI + "/"
+ clientRegElt.getAttribute(ATT_REGISTRATION_ID)));
} }
} }
return entryPoints; return entryPoints;
} }
private RequestMatcher getAuthenticationEntryPointMatcher() {
ContentNegotiationStrategy contentNegotiationStrategy = new HeaderContentNegotiationStrategy();
MediaTypeRequestMatcher mediaMatcher = new MediaTypeRequestMatcher(contentNegotiationStrategy,
MediaType.APPLICATION_XHTML_XML, new MediaType("image", "*"), MediaType.TEXT_HTML,
MediaType.TEXT_PLAIN);
mediaMatcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL));
RequestMatcher notXRequestedWith = new NegatedRequestMatcher(
new RequestHeaderRequestMatcher("X-Requested-With", "XMLHttpRequest"));
return new AndRequestMatcher(Arrays.asList(notXRequestedWith, mediaMatcher));
}
private static class OidcAuthenticationRequestChecker implements AuthenticationProvider { private static class OidcAuthenticationRequestChecker implements AuthenticationProvider {
@Override @Override
@ -463,4 +456,42 @@ final class OAuth2LoginBeanDefinitionParser implements BeanDefinitionParser {
} }
@Deprecated
static class EntryPointMatcherFactoryBean implements FactoryBean<RequestMatcher> {
private final RequestMatcher entryPointMatcher;
EntryPointMatcherFactoryBean(RequestMatcher loginPageMatcher, RequestMatcher faviconMatcher) {
RequestMatcher defaultEntryPointMatcher = getAuthenticationEntryPointMatcher();
RequestMatcher defaultLoginPageMatcher = new AndRequestMatcher(
new OrRequestMatcher(loginPageMatcher, faviconMatcher), defaultEntryPointMatcher);
RequestMatcher notXRequestedWith = new NegatedRequestMatcher(
new RequestHeaderRequestMatcher("X-Requested-With", "XMLHttpRequest"));
this.entryPointMatcher = new AndRequestMatcher(notXRequestedWith,
new NegatedRequestMatcher(defaultLoginPageMatcher));
}
private RequestMatcher getAuthenticationEntryPointMatcher() {
ContentNegotiationStrategy contentNegotiationStrategy = new HeaderContentNegotiationStrategy();
MediaTypeRequestMatcher mediaMatcher = new MediaTypeRequestMatcher(contentNegotiationStrategy,
MediaType.APPLICATION_XHTML_XML, new MediaType("image", "*"), MediaType.TEXT_HTML,
MediaType.TEXT_PLAIN);
mediaMatcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL));
RequestMatcher notXRequestedWith = new NegatedRequestMatcher(
new RequestHeaderRequestMatcher("X-Requested-With", "XMLHttpRequest"));
return new AndRequestMatcher(Arrays.asList(notXRequestedWith, mediaMatcher));
}
@Override
public RequestMatcher getObject() {
return this.entryPointMatcher;
}
@Override
public Class<?> getObjectType() {
return RequestMatcher.class;
}
}
} }