mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-07-12 21:33:30 +00:00
Add Opt-in PathPattern Strategy
Closes gh-16573
This commit is contained in:
parent
588220a020
commit
7d301f87d6
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -219,10 +219,15 @@ public abstract class AbstractRequestMatcherRegistry<C> {
|
|||||||
}
|
}
|
||||||
List<RequestMatcher> matchers = new ArrayList<>();
|
List<RequestMatcher> matchers = new ArrayList<>();
|
||||||
for (String pattern : patterns) {
|
for (String pattern : patterns) {
|
||||||
|
if (RequestMatcherFactory.usesPathPatterns()) {
|
||||||
|
matchers.add(RequestMatcherFactory.matcher(method, pattern));
|
||||||
|
}
|
||||||
|
else {
|
||||||
AntPathRequestMatcher ant = new AntPathRequestMatcher(pattern, (method != null) ? method.name() : null);
|
AntPathRequestMatcher ant = new AntPathRequestMatcher(pattern, (method != null) ? method.name() : null);
|
||||||
MvcRequestMatcher mvc = createMvcMatchers(method, pattern).get(0);
|
MvcRequestMatcher mvc = createMvcMatchers(method, pattern).get(0);
|
||||||
matchers.add(new DeferredRequestMatcher((c) -> resolve(ant, mvc, c), mvc, ant));
|
matchers.add(new DeferredRequestMatcher((c) -> resolve(ant, mvc, c), mvc, ant));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return requestMatchers(matchers.toArray(new RequestMatcher[0]));
|
return requestMatchers(matchers.toArray(new RequestMatcher[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -45,6 +45,7 @@ import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
|
|||||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||||
import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
|
import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
|
||||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
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.EnableWebSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
|
||||||
import org.springframework.security.config.annotation.web.configurers.AnonymousConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.AnonymousConfigurer;
|
||||||
@ -3684,11 +3685,17 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
|||||||
* @see MvcRequestMatcher
|
* @see MvcRequestMatcher
|
||||||
*/
|
*/
|
||||||
public HttpSecurity securityMatcher(String... patterns) {
|
public HttpSecurity securityMatcher(String... patterns) {
|
||||||
if (mvcPresent) {
|
List<RequestMatcher> matchers = new ArrayList<>();
|
||||||
this.requestMatcher = new OrRequestMatcher(createMvcMatchers(patterns));
|
for (String pattern : patterns) {
|
||||||
return this;
|
if (RequestMatcherFactory.usesPathPatterns()) {
|
||||||
|
matchers.add(RequestMatcherFactory.matcher(pattern));
|
||||||
}
|
}
|
||||||
this.requestMatcher = new OrRequestMatcher(createAntMatchers(patterns));
|
else {
|
||||||
|
RequestMatcher matcher = mvcPresent ? createMvcMatcher(pattern) : createAntMatcher(pattern);
|
||||||
|
matchers.add(matcher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.requestMatcher = new OrRequestMatcher(matchers);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3717,15 +3724,11 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
|||||||
return HttpSecurity.this;
|
return HttpSecurity.this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<RequestMatcher> createAntMatchers(String... patterns) {
|
private RequestMatcher createAntMatcher(String pattern) {
|
||||||
List<RequestMatcher> matchers = new ArrayList<>(patterns.length);
|
return new AntPathRequestMatcher(pattern);
|
||||||
for (String pattern : patterns) {
|
|
||||||
matchers.add(new AntPathRequestMatcher(pattern));
|
|
||||||
}
|
|
||||||
return matchers;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<RequestMatcher> createMvcMatchers(String... mvcPatterns) {
|
private RequestMatcher createMvcMatcher(String mvcPattern) {
|
||||||
ResolvableType type = ResolvableType.forClassWithGenerics(ObjectPostProcessor.class, Object.class);
|
ResolvableType type = ResolvableType.forClassWithGenerics(ObjectPostProcessor.class, Object.class);
|
||||||
ObjectProvider<ObjectPostProcessor<Object>> postProcessors = getContext().getBeanProvider(type);
|
ObjectProvider<ObjectPostProcessor<Object>> postProcessors = getContext().getBeanProvider(type);
|
||||||
ObjectPostProcessor<Object> opp = postProcessors.getObject();
|
ObjectPostProcessor<Object> opp = postProcessors.getObject();
|
||||||
@ -3736,13 +3739,9 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
|||||||
}
|
}
|
||||||
HandlerMappingIntrospector introspector = getContext().getBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME,
|
HandlerMappingIntrospector introspector = getContext().getBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME,
|
||||||
HandlerMappingIntrospector.class);
|
HandlerMappingIntrospector.class);
|
||||||
List<RequestMatcher> matchers = new ArrayList<>(mvcPatterns.length);
|
|
||||||
for (String mvcPattern : mvcPatterns) {
|
|
||||||
MvcRequestMatcher matcher = new MvcRequestMatcher(introspector, mvcPattern);
|
MvcRequestMatcher matcher = new MvcRequestMatcher(introspector, mvcPattern);
|
||||||
opp.postProcess(matcher);
|
opp.postProcess(matcher);
|
||||||
matchers.add(matcher);
|
return matcher;
|
||||||
}
|
|
||||||
return matchers;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -35,6 +35,7 @@ import org.springframework.security.config.annotation.authentication.configurati
|
|||||||
import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer;
|
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.provisioning.JdbcUserDetailsManagerConfigurer;
|
||||||
import org.springframework.security.config.annotation.authentication.configurers.userdetails.DaoAuthenticationConfigurer;
|
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.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
import org.springframework.security.config.annotation.web.configurers.DefaultLoginPageConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.DefaultLoginPageConfigurer;
|
||||||
@ -104,6 +105,7 @@ class HttpSecurityConfiguration {
|
|||||||
@Bean(HTTPSECURITY_BEAN_NAME)
|
@Bean(HTTPSECURITY_BEAN_NAME)
|
||||||
@Scope("prototype")
|
@Scope("prototype")
|
||||||
HttpSecurity httpSecurity() throws Exception {
|
HttpSecurity httpSecurity() throws Exception {
|
||||||
|
RequestMatcherFactory.setApplicationContext(this.context);
|
||||||
LazyPasswordEncoder passwordEncoder = new LazyPasswordEncoder(this.context);
|
LazyPasswordEncoder passwordEncoder = new LazyPasswordEncoder(this.context);
|
||||||
AuthenticationManagerBuilder authenticationBuilder = new DefaultPasswordEncoderAuthenticationManagerBuilder(
|
AuthenticationManagerBuilder authenticationBuilder = new DefaultPasswordEncoderAuthenticationManagerBuilder(
|
||||||
this.objectPostProcessor, passwordEncoder);
|
this.objectPostProcessor, passwordEncoder);
|
||||||
|
@ -16,7 +16,9 @@
|
|||||||
|
|
||||||
package org.springframework.security.config.annotation.web.configurers;
|
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.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.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||||
@ -26,7 +28,6 @@ import org.springframework.security.web.authentication.RememberMeServices;
|
|||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
|
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
|
||||||
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
|
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
|
||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
|
||||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -234,7 +235,7 @@ public final class FormLoginConfigurer<H extends HttpSecurityBuilder<H>> extends
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {
|
protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {
|
||||||
return new AntPathRequestMatcher(loginProcessingUrl, "POST");
|
return RequestMatcherFactory.matcher(HttpMethod.POST, loginProcessingUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -22,8 +22,10 @@ import java.util.List;
|
|||||||
|
|
||||||
import jakarta.servlet.http.HttpSession;
|
import jakarta.servlet.http.HttpSession;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.security.config.annotation.SecurityConfigurer;
|
import org.springframework.security.config.annotation.SecurityConfigurer;
|
||||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
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.builders.HttpSecurity;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.web.authentication.logout.CookieClearingLogoutHandler;
|
import org.springframework.security.web.authentication.logout.CookieClearingLogoutHandler;
|
||||||
@ -37,7 +39,6 @@ import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuc
|
|||||||
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
|
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
|
||||||
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
|
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
|
||||||
import org.springframework.security.web.context.SecurityContextRepository;
|
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.OrRequestMatcher;
|
||||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
@ -368,7 +369,7 @@ public final class LogoutConfigurer<H extends HttpSecurityBuilder<H>>
|
|||||||
}
|
}
|
||||||
|
|
||||||
private RequestMatcher createLogoutRequestMatcher(String httpMethod) {
|
private RequestMatcher createLogoutRequestMatcher(String httpMethod) {
|
||||||
return new AntPathRequestMatcher(this.logoutUrl, httpMethod);
|
return RequestMatcherFactory.matcher(HttpMethod.valueOf(httpMethod), this.logoutUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -17,9 +17,9 @@
|
|||||||
package org.springframework.security.config.annotation.web.configurers;
|
package org.springframework.security.config.annotation.web.configurers;
|
||||||
|
|
||||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
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.RequestMatcherRedirectFilter;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -55,7 +55,7 @@ public final class PasswordManagementConfigurer<B extends HttpSecurityBuilder<B>
|
|||||||
@Override
|
@Override
|
||||||
public void configure(B http) throws Exception {
|
public void configure(B http) throws Exception {
|
||||||
RequestMatcherRedirectFilter changePasswordFilter = new RequestMatcherRedirectFilter(
|
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);
|
http.addFilterBefore(postProcess(changePasswordFilter), UsernamePasswordAuthenticationFilter.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,8 +21,10 @@ import java.util.Collections;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
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.builders.HttpSecurity;
|
||||||
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
|
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
|
||||||
import org.springframework.security.web.savedrequest.NullRequestCache;
|
import org.springframework.security.web.savedrequest.NullRequestCache;
|
||||||
@ -140,13 +142,13 @@ public final class RequestCacheConfigurer<H extends HttpSecurityBuilder<H>>
|
|||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private RequestMatcher createDefaultSavedRequestMatcher(H http) {
|
private RequestMatcher createDefaultSavedRequestMatcher(H http) {
|
||||||
RequestMatcher notFavIcon = new NegatedRequestMatcher(new AntPathRequestMatcher("/**/favicon.*"));
|
RequestMatcher notFavIcon = new NegatedRequestMatcher(getFaviconRequestMatcher());
|
||||||
RequestMatcher notXRequestedWith = new NegatedRequestMatcher(
|
RequestMatcher notXRequestedWith = new NegatedRequestMatcher(
|
||||||
new RequestHeaderRequestMatcher("X-Requested-With", "XMLHttpRequest"));
|
new RequestHeaderRequestMatcher("X-Requested-With", "XMLHttpRequest"));
|
||||||
boolean isCsrfEnabled = http.getConfigurer(CsrfConfigurer.class) != null;
|
boolean isCsrfEnabled = http.getConfigurer(CsrfConfigurer.class) != null;
|
||||||
List<RequestMatcher> matchers = new ArrayList<>();
|
List<RequestMatcher> matchers = new ArrayList<>();
|
||||||
if (isCsrfEnabled) {
|
if (isCsrfEnabled) {
|
||||||
RequestMatcher getRequests = new AntPathRequestMatcher("/**", "GET");
|
RequestMatcher getRequests = RequestMatcherFactory.matcher(HttpMethod.GET, "/**");
|
||||||
matchers.add(0, getRequests);
|
matchers.add(0, getRequests);
|
||||||
}
|
}
|
||||||
matchers.add(notFavIcon);
|
matchers.add(notFavIcon);
|
||||||
@ -167,4 +169,13 @@ public final class RequestCacheConfigurer<H extends HttpSecurityBuilder<H>>
|
|||||||
return new NegatedRequestMatcher(mediaRequest);
|
return new NegatedRequestMatcher(mediaRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private RequestMatcher getFaviconRequestMatcher() {
|
||||||
|
if (RequestMatcherFactory.usesPathPatterns()) {
|
||||||
|
return RequestMatcherFactory.matcher("/favicon.*");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return new AntPathRequestMatcher("/**/favicon.*");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,7 @@ import org.springframework.core.ResolvableType;
|
|||||||
import org.springframework.security.authentication.AuthenticationProvider;
|
import org.springframework.security.authentication.AuthenticationProvider;
|
||||||
import org.springframework.security.config.Customizer;
|
import org.springframework.security.config.Customizer;
|
||||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
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.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
|
||||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
@ -91,7 +92,6 @@ import org.springframework.security.web.authentication.ui.DefaultLoginPageGenera
|
|||||||
import org.springframework.security.web.csrf.CsrfToken;
|
import org.springframework.security.web.csrf.CsrfToken;
|
||||||
import org.springframework.security.web.savedrequest.RequestCache;
|
import org.springframework.security.web.savedrequest.RequestCache;
|
||||||
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.AnyRequestMatcher;
|
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
|
||||||
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;
|
||||||
@ -431,7 +431,7 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {
|
protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {
|
||||||
return new AntPathRequestMatcher(loginProcessingUrl);
|
return RequestMatcherFactory.matcher(loginProcessingUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
private OAuth2AuthorizationRequestResolver getAuthorizationRequestResolver() {
|
private OAuth2AuthorizationRequestResolver getAuthorizationRequestResolver() {
|
||||||
@ -569,8 +569,8 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||||||
}
|
}
|
||||||
|
|
||||||
private AuthenticationEntryPoint getLoginEntryPoint(B http, String providerLoginPage) {
|
private AuthenticationEntryPoint getLoginEntryPoint(B http, String providerLoginPage) {
|
||||||
RequestMatcher loginPageMatcher = new AntPathRequestMatcher(this.getLoginPage());
|
RequestMatcher loginPageMatcher = RequestMatcherFactory.matcher(this.getLoginPage());
|
||||||
RequestMatcher faviconMatcher = new AntPathRequestMatcher("/favicon.ico");
|
RequestMatcher faviconMatcher = RequestMatcherFactory.matcher("/favicon.ico");
|
||||||
RequestMatcher defaultEntryPointMatcher = this.getAuthenticationEntryPointMatcher(http);
|
RequestMatcher defaultEntryPointMatcher = this.getAuthenticationEntryPointMatcher(http);
|
||||||
RequestMatcher defaultLoginPageMatcher = new AndRequestMatcher(
|
RequestMatcher defaultLoginPageMatcher = new AndRequestMatcher(
|
||||||
new OrRequestMatcher(loginPageMatcher, faviconMatcher), defaultEntryPointMatcher);
|
new OrRequestMatcher(loginPageMatcher, faviconMatcher), defaultEntryPointMatcher);
|
||||||
|
@ -31,6 +31,7 @@ import org.springframework.security.authentication.ott.OneTimeTokenAuthenticatio
|
|||||||
import org.springframework.security.authentication.ott.OneTimeTokenService;
|
import org.springframework.security.authentication.ott.OneTimeTokenService;
|
||||||
import org.springframework.security.config.Customizer;
|
import org.springframework.security.config.Customizer;
|
||||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
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.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
|
||||||
@ -56,8 +57,6 @@ import org.springframework.security.web.util.matcher.RequestMatcher;
|
|||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An {@link AbstractHttpConfigurer} for One-Time Token Login.
|
* An {@link AbstractHttpConfigurer} for One-Time Token Login.
|
||||||
*
|
*
|
||||||
@ -163,7 +162,7 @@ public final class OneTimeTokenLoginConfigurer<H extends HttpSecurityBuilder<H>>
|
|||||||
private void configureOttGenerateFilter(H http) {
|
private void configureOttGenerateFilter(H http) {
|
||||||
GenerateOneTimeTokenFilter generateFilter = new GenerateOneTimeTokenFilter(getOneTimeTokenService(),
|
GenerateOneTimeTokenFilter generateFilter = new GenerateOneTimeTokenFilter(getOneTimeTokenService(),
|
||||||
getOneTimeTokenGenerationSuccessHandler());
|
getOneTimeTokenGenerationSuccessHandler());
|
||||||
generateFilter.setRequestMatcher(antMatcher(HttpMethod.POST, this.tokenGeneratingUrl));
|
generateFilter.setRequestMatcher(RequestMatcherFactory.matcher(HttpMethod.POST, this.tokenGeneratingUrl));
|
||||||
generateFilter.setRequestResolver(getGenerateRequestResolver());
|
generateFilter.setRequestResolver(getGenerateRequestResolver());
|
||||||
http.addFilter(postProcess(generateFilter));
|
http.addFilter(postProcess(generateFilter));
|
||||||
http.addFilter(DefaultResourcesFilter.css());
|
http.addFilter(DefaultResourcesFilter.css());
|
||||||
@ -190,7 +189,7 @@ public final class OneTimeTokenLoginConfigurer<H extends HttpSecurityBuilder<H>>
|
|||||||
}
|
}
|
||||||
DefaultOneTimeTokenSubmitPageGeneratingFilter submitPage = new DefaultOneTimeTokenSubmitPageGeneratingFilter();
|
DefaultOneTimeTokenSubmitPageGeneratingFilter submitPage = new DefaultOneTimeTokenSubmitPageGeneratingFilter();
|
||||||
submitPage.setResolveHiddenInputs(this::hiddenInputs);
|
submitPage.setResolveHiddenInputs(this::hiddenInputs);
|
||||||
submitPage.setRequestMatcher(antMatcher(HttpMethod.GET, this.defaultSubmitPageUrl));
|
submitPage.setRequestMatcher(RequestMatcherFactory.matcher(HttpMethod.GET, this.defaultSubmitPageUrl));
|
||||||
submitPage.setLoginProcessingUrl(this.getLoginProcessingUrl());
|
submitPage.setLoginProcessingUrl(this.getLoginProcessingUrl());
|
||||||
http.addFilter(postProcess(submitPage));
|
http.addFilter(postProcess(submitPage));
|
||||||
}
|
}
|
||||||
@ -207,7 +206,7 @@ public final class OneTimeTokenLoginConfigurer<H extends HttpSecurityBuilder<H>>
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {
|
protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {
|
||||||
return antMatcher(HttpMethod.POST, loginProcessingUrl);
|
return RequestMatcherFactory.matcher(HttpMethod.POST, loginProcessingUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,6 +28,7 @@ import org.springframework.context.ApplicationContext;
|
|||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
import org.springframework.security.authentication.AuthenticationProvider;
|
import org.springframework.security.authentication.AuthenticationProvider;
|
||||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
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.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
|
||||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
@ -56,7 +57,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.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;
|
||||||
import org.springframework.security.web.util.matcher.ParameterRequestMatcher;
|
import org.springframework.security.web.util.matcher.ParameterRequestMatcher;
|
||||||
@ -127,15 +127,11 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||||||
|
|
||||||
private String[] authenticationRequestParams = { "registrationId={registrationId}" };
|
private String[] authenticationRequestParams = { "registrationId={registrationId}" };
|
||||||
|
|
||||||
private RequestMatcher authenticationRequestMatcher = RequestMatchers.anyOf(
|
private RequestMatcher authenticationRequestMatcher;
|
||||||
new AntPathRequestMatcher(Saml2AuthenticationRequestResolver.DEFAULT_AUTHENTICATION_REQUEST_URI),
|
|
||||||
new AntPathQueryRequestMatcher(this.authenticationRequestUri, this.authenticationRequestParams));
|
|
||||||
|
|
||||||
private Saml2AuthenticationRequestResolver authenticationRequestResolver;
|
private Saml2AuthenticationRequestResolver authenticationRequestResolver;
|
||||||
|
|
||||||
private RequestMatcher loginProcessingUrl = RequestMatchers.anyOf(
|
private RequestMatcher loginProcessingUrl;
|
||||||
new AntPathRequestMatcher(Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI),
|
|
||||||
new AntPathRequestMatcher("/login/saml2/sso"));
|
|
||||||
|
|
||||||
private RelyingPartyRegistrationRepository relyingPartyRegistrationRepository;
|
private RelyingPartyRegistrationRepository relyingPartyRegistrationRepository;
|
||||||
|
|
||||||
@ -238,8 +234,8 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||||||
this.authenticationRequestUri = parts[0];
|
this.authenticationRequestUri = parts[0];
|
||||||
this.authenticationRequestParams = new String[parts.length - 1];
|
this.authenticationRequestParams = new String[parts.length - 1];
|
||||||
System.arraycopy(parts, 1, this.authenticationRequestParams, 0, parts.length - 1);
|
System.arraycopy(parts, 1, this.authenticationRequestParams, 0, parts.length - 1);
|
||||||
this.authenticationRequestMatcher = new AntPathQueryRequestMatcher(this.authenticationRequestUri,
|
this.authenticationRequestMatcher = new PathQueryRequestMatcher(
|
||||||
this.authenticationRequestParams);
|
RequestMatcherFactory.matcher(this.authenticationRequestUri), this.authenticationRequestParams);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,13 +252,13 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||||||
@Override
|
@Override
|
||||||
public Saml2LoginConfigurer<B> loginProcessingUrl(String loginProcessingUrl) {
|
public Saml2LoginConfigurer<B> loginProcessingUrl(String loginProcessingUrl) {
|
||||||
Assert.hasText(loginProcessingUrl, "loginProcessingUrl cannot be empty");
|
Assert.hasText(loginProcessingUrl, "loginProcessingUrl cannot be empty");
|
||||||
this.loginProcessingUrl = new AntPathRequestMatcher(loginProcessingUrl);
|
this.loginProcessingUrl = RequestMatcherFactory.matcher(loginProcessingUrl);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {
|
protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {
|
||||||
return new AntPathRequestMatcher(loginProcessingUrl);
|
return RequestMatcherFactory.matcher(loginProcessingUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -284,7 +280,7 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||||||
relyingPartyRegistrationRepository(http);
|
relyingPartyRegistrationRepository(http);
|
||||||
this.saml2WebSsoAuthenticationFilter = new Saml2WebSsoAuthenticationFilter(getAuthenticationConverter(http));
|
this.saml2WebSsoAuthenticationFilter = new Saml2WebSsoAuthenticationFilter(getAuthenticationConverter(http));
|
||||||
this.saml2WebSsoAuthenticationFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
|
this.saml2WebSsoAuthenticationFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
|
||||||
this.saml2WebSsoAuthenticationFilter.setRequiresAuthenticationRequestMatcher(this.loginProcessingUrl);
|
this.saml2WebSsoAuthenticationFilter.setRequiresAuthenticationRequestMatcher(getLoginProcessingEndpoint());
|
||||||
setAuthenticationRequestRepository(http, this.saml2WebSsoAuthenticationFilter);
|
setAuthenticationRequestRepository(http, this.saml2WebSsoAuthenticationFilter);
|
||||||
setAuthenticationFilter(this.saml2WebSsoAuthenticationFilter);
|
setAuthenticationFilter(this.saml2WebSsoAuthenticationFilter);
|
||||||
if (StringUtils.hasText(this.loginPage)) {
|
if (StringUtils.hasText(this.loginPage)) {
|
||||||
@ -340,8 +336,8 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||||||
}
|
}
|
||||||
|
|
||||||
private AuthenticationEntryPoint getLoginEntryPoint(B http, String providerLoginPage) {
|
private AuthenticationEntryPoint getLoginEntryPoint(B http, String providerLoginPage) {
|
||||||
RequestMatcher loginPageMatcher = new AntPathRequestMatcher(this.getLoginPage());
|
RequestMatcher loginPageMatcher = RequestMatcherFactory.matcher(this.getLoginPage());
|
||||||
RequestMatcher faviconMatcher = new AntPathRequestMatcher("/favicon.ico");
|
RequestMatcher faviconMatcher = RequestMatcherFactory.matcher("/favicon.ico");
|
||||||
RequestMatcher defaultEntryPointMatcher = this.getAuthenticationEntryPointMatcher(http);
|
RequestMatcher defaultEntryPointMatcher = this.getAuthenticationEntryPointMatcher(http);
|
||||||
RequestMatcher defaultLoginPageMatcher = new AndRequestMatcher(
|
RequestMatcher defaultLoginPageMatcher = new AndRequestMatcher(
|
||||||
new OrRequestMatcher(loginPageMatcher, faviconMatcher), defaultEntryPointMatcher);
|
new OrRequestMatcher(loginPageMatcher, faviconMatcher), defaultEntryPointMatcher);
|
||||||
@ -376,17 +372,38 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||||||
if (USE_OPENSAML_5) {
|
if (USE_OPENSAML_5) {
|
||||||
OpenSaml5AuthenticationRequestResolver openSamlAuthenticationRequestResolver = new OpenSaml5AuthenticationRequestResolver(
|
OpenSaml5AuthenticationRequestResolver openSamlAuthenticationRequestResolver = new OpenSaml5AuthenticationRequestResolver(
|
||||||
relyingPartyRegistrationRepository(http));
|
relyingPartyRegistrationRepository(http));
|
||||||
openSamlAuthenticationRequestResolver.setRequestMatcher(this.authenticationRequestMatcher);
|
openSamlAuthenticationRequestResolver.setRequestMatcher(getAuthenticationRequestMatcher());
|
||||||
return openSamlAuthenticationRequestResolver;
|
return openSamlAuthenticationRequestResolver;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
OpenSaml4AuthenticationRequestResolver openSamlAuthenticationRequestResolver = new OpenSaml4AuthenticationRequestResolver(
|
OpenSaml4AuthenticationRequestResolver openSamlAuthenticationRequestResolver = new OpenSaml4AuthenticationRequestResolver(
|
||||||
relyingPartyRegistrationRepository(http));
|
relyingPartyRegistrationRepository(http));
|
||||||
openSamlAuthenticationRequestResolver.setRequestMatcher(this.authenticationRequestMatcher);
|
openSamlAuthenticationRequestResolver.setRequestMatcher(getAuthenticationRequestMatcher());
|
||||||
return openSamlAuthenticationRequestResolver;
|
return openSamlAuthenticationRequestResolver;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private RequestMatcher getAuthenticationRequestMatcher() {
|
||||||
|
if (this.authenticationRequestMatcher == null) {
|
||||||
|
this.authenticationRequestMatcher = RequestMatchers.anyOf(
|
||||||
|
RequestMatcherFactory
|
||||||
|
.matcher(Saml2AuthenticationRequestResolver.DEFAULT_AUTHENTICATION_REQUEST_URI),
|
||||||
|
new PathQueryRequestMatcher(RequestMatcherFactory.matcher(this.authenticationRequestUri),
|
||||||
|
this.authenticationRequestParams));
|
||||||
|
}
|
||||||
|
return this.authenticationRequestMatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RequestMatcher getLoginProcessingEndpoint() {
|
||||||
|
if (this.loginProcessingUrl == null) {
|
||||||
|
this.loginProcessingUrl = RequestMatchers.anyOf(
|
||||||
|
RequestMatcherFactory.matcher(Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI),
|
||||||
|
RequestMatcherFactory.matcher("/login/saml2/sso"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.loginProcessingUrl;
|
||||||
|
}
|
||||||
|
|
||||||
private AuthenticationConverter getAuthenticationConverter(B http) {
|
private AuthenticationConverter getAuthenticationConverter(B http) {
|
||||||
if (this.authenticationConverter != null) {
|
if (this.authenticationConverter != null) {
|
||||||
return this.authenticationConverter;
|
return this.authenticationConverter;
|
||||||
@ -407,7 +424,7 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||||||
OpenSaml5AuthenticationTokenConverter converter = new OpenSaml5AuthenticationTokenConverter(
|
OpenSaml5AuthenticationTokenConverter converter = new OpenSaml5AuthenticationTokenConverter(
|
||||||
this.relyingPartyRegistrationRepository);
|
this.relyingPartyRegistrationRepository);
|
||||||
converter.setAuthenticationRequestRepository(getAuthenticationRequestRepository(http));
|
converter.setAuthenticationRequestRepository(getAuthenticationRequestRepository(http));
|
||||||
converter.setRequestMatcher(this.loginProcessingUrl);
|
converter.setRequestMatcher(getLoginProcessingEndpoint());
|
||||||
return converter;
|
return converter;
|
||||||
}
|
}
|
||||||
authenticationConverterBean = getBeanOrNull(http, OpenSaml4AuthenticationTokenConverter.class);
|
authenticationConverterBean = getBeanOrNull(http, OpenSaml4AuthenticationTokenConverter.class);
|
||||||
@ -417,7 +434,7 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||||||
OpenSaml4AuthenticationTokenConverter converter = new OpenSaml4AuthenticationTokenConverter(
|
OpenSaml4AuthenticationTokenConverter converter = new OpenSaml4AuthenticationTokenConverter(
|
||||||
this.relyingPartyRegistrationRepository);
|
this.relyingPartyRegistrationRepository);
|
||||||
converter.setAuthenticationRequestRepository(getAuthenticationRequestRepository(http));
|
converter.setAuthenticationRequestRepository(getAuthenticationRequestRepository(http));
|
||||||
converter.setRequestMatcher(this.loginProcessingUrl);
|
converter.setRequestMatcher(getLoginProcessingEndpoint());
|
||||||
return converter;
|
return converter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -441,7 +458,7 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||||||
if (csrf == null) {
|
if (csrf == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
csrf.ignoringRequestMatchers(this.loginProcessingUrl);
|
csrf.ignoringRequestMatchers(getLoginProcessingEndpoint());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initDefaultLoginFilter(B http) {
|
private void initDefaultLoginFilter(B http) {
|
||||||
@ -509,13 +526,13 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class AntPathQueryRequestMatcher implements RequestMatcher {
|
static class PathQueryRequestMatcher implements RequestMatcher {
|
||||||
|
|
||||||
private final RequestMatcher matcher;
|
private final RequestMatcher matcher;
|
||||||
|
|
||||||
AntPathQueryRequestMatcher(String path, String... params) {
|
PathQueryRequestMatcher(RequestMatcher pathMatcher, String... params) {
|
||||||
List<RequestMatcher> matchers = new ArrayList<>();
|
List<RequestMatcher> matchers = new ArrayList<>();
|
||||||
matchers.add(new AntPathRequestMatcher(path));
|
matchers.add(pathMatcher);
|
||||||
for (String param : params) {
|
for (String param : params) {
|
||||||
String[] parts = param.split("=");
|
String[] parts = param.split("=");
|
||||||
if (parts.length == 1) {
|
if (parts.length == 1) {
|
||||||
|
@ -23,9 +23,11 @@ import jakarta.servlet.http.HttpServletRequest;
|
|||||||
import org.opensaml.core.Version;
|
import org.opensaml.core.Version;
|
||||||
|
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
import org.springframework.security.config.Customizer;
|
import org.springframework.security.config.Customizer;
|
||||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
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.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer;
|
||||||
@ -64,7 +66,6 @@ import org.springframework.security.web.csrf.CsrfFilter;
|
|||||||
import org.springframework.security.web.csrf.CsrfLogoutHandler;
|
import org.springframework.security.web.csrf.CsrfLogoutHandler;
|
||||||
import org.springframework.security.web.csrf.CsrfTokenRepository;
|
import org.springframework.security.web.csrf.CsrfTokenRepository;
|
||||||
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.ParameterRequestMatcher;
|
import org.springframework.security.web.util.matcher.ParameterRequestMatcher;
|
||||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||||
|
|
||||||
@ -304,19 +305,19 @@ public final class Saml2LogoutConfigurer<H extends HttpSecurityBuilder<H>>
|
|||||||
}
|
}
|
||||||
|
|
||||||
private RequestMatcher createLogoutMatcher() {
|
private RequestMatcher createLogoutMatcher() {
|
||||||
RequestMatcher logout = new AntPathRequestMatcher(this.logoutUrl, "POST");
|
RequestMatcher logout = RequestMatcherFactory.matcher(HttpMethod.POST, this.logoutUrl);
|
||||||
RequestMatcher saml2 = new Saml2RequestMatcher(getSecurityContextHolderStrategy());
|
RequestMatcher saml2 = new Saml2RequestMatcher(getSecurityContextHolderStrategy());
|
||||||
return new AndRequestMatcher(logout, saml2);
|
return new AndRequestMatcher(logout, saml2);
|
||||||
}
|
}
|
||||||
|
|
||||||
private RequestMatcher createLogoutRequestMatcher() {
|
private RequestMatcher createLogoutRequestMatcher() {
|
||||||
RequestMatcher logout = new AntPathRequestMatcher(this.logoutRequestConfigurer.logoutUrl);
|
RequestMatcher logout = RequestMatcherFactory.matcher(this.logoutRequestConfigurer.logoutUrl);
|
||||||
RequestMatcher samlRequest = new ParameterRequestMatcher("SAMLRequest");
|
RequestMatcher samlRequest = new ParameterRequestMatcher("SAMLRequest");
|
||||||
return new AndRequestMatcher(logout, samlRequest);
|
return new AndRequestMatcher(logout, samlRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
private RequestMatcher createLogoutResponseMatcher() {
|
private RequestMatcher createLogoutResponseMatcher() {
|
||||||
RequestMatcher logout = new AntPathRequestMatcher(this.logoutResponseConfigurer.logoutUrl);
|
RequestMatcher logout = RequestMatcherFactory.matcher(this.logoutResponseConfigurer.logoutUrl);
|
||||||
RequestMatcher samlResponse = new ParameterRequestMatcher("SAMLResponse");
|
RequestMatcher samlResponse = new ParameterRequestMatcher("SAMLResponse");
|
||||||
return new AndRequestMatcher(logout, samlResponse);
|
return new AndRequestMatcher(logout, samlResponse);
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import org.opensaml.core.Version;
|
|||||||
|
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
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.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
import org.springframework.security.saml2.provider.service.metadata.OpenSaml4MetadataResolver;
|
import org.springframework.security.saml2.provider.service.metadata.OpenSaml4MetadataResolver;
|
||||||
@ -32,7 +33,6 @@ import org.springframework.security.saml2.provider.service.registration.RelyingP
|
|||||||
import org.springframework.security.saml2.provider.service.web.Saml2MetadataFilter;
|
import org.springframework.security.saml2.provider.service.web.Saml2MetadataFilter;
|
||||||
import org.springframework.security.saml2.provider.service.web.metadata.RequestMatcherMetadataResponseResolver;
|
import org.springframework.security.saml2.provider.service.web.metadata.RequestMatcherMetadataResponseResolver;
|
||||||
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
|
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
|
||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -111,12 +111,12 @@ public class Saml2MetadataConfigurer<H extends HttpSecurityBuilder<H>>
|
|||||||
if (USE_OPENSAML_5) {
|
if (USE_OPENSAML_5) {
|
||||||
RequestMatcherMetadataResponseResolver metadata = new RequestMatcherMetadataResponseResolver(
|
RequestMatcherMetadataResponseResolver metadata = new RequestMatcherMetadataResponseResolver(
|
||||||
registrations, new OpenSaml5MetadataResolver());
|
registrations, new OpenSaml5MetadataResolver());
|
||||||
metadata.setRequestMatcher(new AntPathRequestMatcher(metadataUrl));
|
metadata.setRequestMatcher(RequestMatcherFactory.matcher(metadataUrl));
|
||||||
return metadata;
|
return metadata;
|
||||||
}
|
}
|
||||||
RequestMatcherMetadataResponseResolver metadata = new RequestMatcherMetadataResponseResolver(registrations,
|
RequestMatcherMetadataResponseResolver metadata = new RequestMatcherMetadataResponseResolver(registrations,
|
||||||
new OpenSaml4MetadataResolver());
|
new OpenSaml4MetadataResolver());
|
||||||
metadata.setRequestMatcher(new AntPathRequestMatcher(metadataUrl));
|
metadata.setRequestMatcher(RequestMatcherFactory.matcher(metadataUrl));
|
||||||
return metadata;
|
return metadata;
|
||||||
};
|
};
|
||||||
return this;
|
return this;
|
||||||
|
@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
* 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.web;
|
||||||
|
|
||||||
|
import reactor.util.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.springframework.beans.BeansException;
|
||||||
|
import org.springframework.beans.factory.BeanFactory;
|
||||||
|
import org.springframework.beans.factory.BeanFactoryAware;
|
||||||
|
import org.springframework.beans.factory.BeanNameAware;
|
||||||
|
import org.springframework.beans.factory.FactoryBean;
|
||||||
|
import org.springframework.beans.factory.config.BeanDefinition;
|
||||||
|
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.context.ApplicationContextAware;
|
||||||
|
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
|
||||||
|
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
|
||||||
|
import org.springframework.web.util.pattern.PathPatternParser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this factory bean to configure the {@link PathPatternRequestMatcher.Builder} bean
|
||||||
|
* used to create request matchers in {@link AuthorizeHttpRequestsConfigurer} and other
|
||||||
|
* parts of the DSL.
|
||||||
|
*
|
||||||
|
* @author Josh Cummings
|
||||||
|
* @since 6.5
|
||||||
|
*/
|
||||||
|
public final class PathPatternRequestMatcherBuilderFactoryBean implements
|
||||||
|
FactoryBean<PathPatternRequestMatcher.Builder>, ApplicationContextAware, BeanNameAware, BeanFactoryAware {
|
||||||
|
|
||||||
|
static final String MVC_PATTERN_PARSER_BEAN_NAME = "mvcPatternParser";
|
||||||
|
|
||||||
|
private final PathPatternParser parser;
|
||||||
|
|
||||||
|
private ApplicationContext context;
|
||||||
|
|
||||||
|
private String beanName;
|
||||||
|
|
||||||
|
private ConfigurableListableBeanFactory beanFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct this factory bean using the default {@link PathPatternParser}
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* If you are using Spring MVC, it will use the Spring MVC instance.
|
||||||
|
*/
|
||||||
|
public PathPatternRequestMatcherBuilderFactoryBean() {
|
||||||
|
this(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct this factory bean using this {@link PathPatternParser}.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* If you are using Spring MVC, it is likely incorrect to call this constructor.
|
||||||
|
* Please call the default constructor instead.
|
||||||
|
* @param parser the {@link PathPatternParser} to use
|
||||||
|
*/
|
||||||
|
public PathPatternRequestMatcherBuilderFactoryBean(PathPatternParser parser) {
|
||||||
|
this.parser = parser;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> getObjectType() {
|
||||||
|
return PathPatternRequestMatcher.Builder.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setApplicationContext(ApplicationContext context) throws BeansException {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBeanName(@NonNull String name) {
|
||||||
|
this.beanName = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||||
|
if (beanFactory instanceof ConfigurableListableBeanFactory listable) {
|
||||||
|
this.beanFactory = listable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String describe(Object bean, String name) {
|
||||||
|
String text = bean.getClass().getSimpleName();
|
||||||
|
if (name == null) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
text += "defined as '" + name + "'";
|
||||||
|
if (this.beanFactory == null) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
BeanDefinition bd = this.beanFactory.getBeanDefinition(name);
|
||||||
|
String description = bd.getResourceDescription();
|
||||||
|
if (description == null) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
text += " in [" + description + "]";
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -51,6 +51,7 @@ import org.springframework.security.config.core.GrantedAuthorityDefaults;
|
|||||||
import org.springframework.security.config.observation.SecurityObservationSettings;
|
import org.springframework.security.config.observation.SecurityObservationSettings;
|
||||||
import org.springframework.security.config.test.SpringTestContext;
|
import org.springframework.security.config.test.SpringTestContext;
|
||||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||||
|
import org.springframework.security.config.web.PathPatternRequestMatcherBuilderFactoryBean;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.authority.AuthorityUtils;
|
import org.springframework.security.core.authority.AuthorityUtils;
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
@ -64,6 +65,7 @@ import org.springframework.security.web.access.intercept.AuthorizationFilter;
|
|||||||
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
|
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
|
||||||
import org.springframework.security.web.access.intercept.RequestMatcherDelegatingAuthorizationManager;
|
import org.springframework.security.web.access.intercept.RequestMatcherDelegatingAuthorizationManager;
|
||||||
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
|
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
|
||||||
|
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
|
||||||
import org.springframework.test.web.servlet.MockMvc;
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
|
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
|
||||||
import org.springframework.test.web.servlet.request.RequestPostProcessor;
|
import org.springframework.test.web.servlet.request.RequestPostProcessor;
|
||||||
@ -72,6 +74,7 @@ import org.springframework.web.bind.annotation.PathVariable;
|
|||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.servlet.DispatcherServlet;
|
||||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||||
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
|
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
|
||||||
|
|
||||||
@ -667,6 +670,26 @@ public class AuthorizeHttpRequestsConfigurerTests {
|
|||||||
verifyNoInteractions(handler);
|
verifyNoInteractions(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void requestMatchersWhenMultipleDispatcherServletsAndPathBeanThenAllows() throws Exception {
|
||||||
|
this.spring.register(MvcRequestMatcherBuilderConfig.class, BasicController.class)
|
||||||
|
.postProcessor((context) -> context.getServletContext()
|
||||||
|
.addServlet("otherDispatcherServlet", DispatcherServlet.class)
|
||||||
|
.addMapping("/mvc"))
|
||||||
|
.autowire();
|
||||||
|
this.mvc.perform(get("/mvc/path").servletPath("/mvc").with(user("user"))).andExpect(status().isOk());
|
||||||
|
this.mvc.perform(get("/mvc/path").servletPath("/mvc").with(user("user").roles("DENIED")))
|
||||||
|
.andExpect(status().isForbidden());
|
||||||
|
this.mvc.perform(get("/path").with(user("user"))).andExpect(status().isForbidden());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void requestMatchersWhenFactoryBeanThenAuthorizes() throws Exception {
|
||||||
|
this.spring.register(PathPatternFactoryBeanConfig.class).autowire();
|
||||||
|
this.mvc.perform(get("/path/resource")).andExpect(status().isUnauthorized());
|
||||||
|
this.mvc.perform(get("/path/resource").with(user("user").roles("USER"))).andExpect(status().isNotFound());
|
||||||
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
static class GrantedAuthorityDefaultHasRoleConfig {
|
static class GrantedAuthorityDefaultHasRoleConfig {
|
||||||
@ -1262,6 +1285,10 @@ public class AuthorizeHttpRequestsConfigurerTests {
|
|||||||
void rootPost() {
|
void rootPost() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/path")
|
||||||
|
void path() {
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@ -1317,4 +1344,50 @@ public class AuthorizeHttpRequestsConfigurerTests {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
@EnableWebMvc
|
||||||
|
static class MvcRequestMatcherBuilderConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
SecurityFilterChain security(HttpSecurity http) throws Exception {
|
||||||
|
PathPatternRequestMatcher.Builder mvc = PathPatternRequestMatcher.withDefaults().servletPath("/mvc");
|
||||||
|
// @formatter:off
|
||||||
|
http
|
||||||
|
.authorizeHttpRequests((authorize) -> authorize
|
||||||
|
.requestMatchers(mvc.matcher("/path/**")).hasRole("USER")
|
||||||
|
)
|
||||||
|
.httpBasic(withDefaults());
|
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
@EnableWebMvc
|
||||||
|
static class PathPatternFactoryBeanConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
SecurityFilterChain security(HttpSecurity http) throws Exception {
|
||||||
|
// @formatter:off
|
||||||
|
http
|
||||||
|
.authorizeHttpRequests((authorize) -> authorize
|
||||||
|
.requestMatchers("/path/**").hasRole("USER")
|
||||||
|
)
|
||||||
|
.httpBasic(withDefaults());
|
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
PathPatternRequestMatcherBuilderFactoryBean pathPatternFactoryBean() {
|
||||||
|
return new PathPatternRequestMatcherBuilderFactoryBean();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,7 @@ 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.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.test.SpringTestContext;
|
import org.springframework.security.config.test.SpringTestContext;
|
||||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||||
|
import org.springframework.security.config.web.PathPatternRequestMatcherBuilderFactoryBean;
|
||||||
import org.springframework.security.core.userdetails.User;
|
import org.springframework.security.core.userdetails.User;
|
||||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||||
import org.springframework.security.test.web.servlet.RequestCacheResultMatcher;
|
import org.springframework.security.test.web.servlet.RequestCacheResultMatcher;
|
||||||
@ -291,6 +292,22 @@ public class RequestCacheConfigurerTests {
|
|||||||
this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/"));
|
this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getWhenPathPatternFactoryBeanThenFaviconIcoRedirectsToRoot() throws Exception {
|
||||||
|
this.spring
|
||||||
|
.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class, PathPatternFactoryBeanConfig.class)
|
||||||
|
.autowire();
|
||||||
|
// @formatter:off
|
||||||
|
MockHttpSession session = (MockHttpSession) this.mvc.perform(get("/favicon.ico"))
|
||||||
|
.andExpect(redirectedUrl("http://localhost/login"))
|
||||||
|
.andReturn()
|
||||||
|
.getRequest()
|
||||||
|
.getSession();
|
||||||
|
// @formatter:on
|
||||||
|
// ignores favicon.ico
|
||||||
|
this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/"));
|
||||||
|
}
|
||||||
|
|
||||||
private static RequestBuilder formLogin(MockHttpSession session) {
|
private static RequestBuilder formLogin(MockHttpSession session) {
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
return post("/login")
|
return post("/login")
|
||||||
@ -470,4 +487,15 @@ public class RequestCacheConfigurerTests {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
static class PathPatternFactoryBeanConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
PathPatternRequestMatcherBuilderFactoryBean factoryBean() {
|
||||||
|
return new PathPatternRequestMatcherBuilderFactoryBean();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* 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.web;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
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.util.matcher.PathPatternRequestMatcher;
|
||||||
|
import org.springframework.web.util.pattern.PathPatternParser;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class PathPatternRequestMatcherBuilderFactoryBeanTests {
|
||||||
|
|
||||||
|
GenericApplicationContext context;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
this.context = new GenericApplicationContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getObjectWhenDefaultsThenBuilder() throws Exception {
|
||||||
|
factoryBean().getObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getObjectWhenMvcPatternParserThenUses() throws Exception {
|
||||||
|
PathPatternParser mvc = registerMvcPatternParser();
|
||||||
|
PathPatternRequestMatcher.Builder builder = factoryBean().getObject();
|
||||||
|
builder.matcher("/path/**");
|
||||||
|
verify(mvc).parse("/path/**");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getObjectWhenPathPatternParserThenUses() throws Exception {
|
||||||
|
PathPatternParser parser = mock(PathPatternParser.class);
|
||||||
|
PathPatternRequestMatcher.Builder builder = factoryBean(parser).getObject();
|
||||||
|
builder.matcher("/path/**");
|
||||||
|
verify(parser).parse("/path/**");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getObjectWhenMvcAndPathPatternParserConflictThenIllegalArgument() {
|
||||||
|
registerMvcPatternParser();
|
||||||
|
PathPatternParser parser = mock(PathPatternParser.class);
|
||||||
|
assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> factoryBean(parser).getObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getObjectWhenMvcAndPathPatternParserAgreeThenUses() throws Exception {
|
||||||
|
PathPatternParser mvc = registerMvcPatternParser();
|
||||||
|
PathPatternRequestMatcher.Builder builder = factoryBean(mvc).getObject();
|
||||||
|
builder.matcher("/path/**");
|
||||||
|
verify(mvc).parse("/path/**");
|
||||||
|
}
|
||||||
|
|
||||||
|
PathPatternRequestMatcherBuilderFactoryBean factoryBean() {
|
||||||
|
PathPatternRequestMatcherBuilderFactoryBean factoryBean = new PathPatternRequestMatcherBuilderFactoryBean();
|
||||||
|
factoryBean.setApplicationContext(this.context);
|
||||||
|
return factoryBean;
|
||||||
|
}
|
||||||
|
|
||||||
|
PathPatternRequestMatcherBuilderFactoryBean factoryBean(PathPatternParser parser) {
|
||||||
|
PathPatternRequestMatcherBuilderFactoryBean factoryBean = new PathPatternRequestMatcherBuilderFactoryBean(
|
||||||
|
parser);
|
||||||
|
factoryBean.setApplicationContext(this.context);
|
||||||
|
return factoryBean;
|
||||||
|
}
|
||||||
|
|
||||||
|
PathPatternParser registerMvcPatternParser() {
|
||||||
|
PathPatternParser mvc = mock(PathPatternParser.class);
|
||||||
|
this.context.registerBean(PathPatternRequestMatcherBuilderFactoryBean.MVC_PATTERN_PARSER_BEAN_NAME,
|
||||||
|
PathPatternParser.class, () -> mvc);
|
||||||
|
this.context.refresh();
|
||||||
|
return mvc;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
92
docs/modules/ROOT/pages/migration/web.adoc
Normal file
92
docs/modules/ROOT/pages/migration/web.adoc
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
= Web Migrations
|
||||||
|
|
||||||
|
[[use-path-pattern]]
|
||||||
|
== Use PathPatternRequestMatcher by Default
|
||||||
|
|
||||||
|
In Spring Security 7, `AntPathRequestMatcher` and `MvcRequestMatcher` are no longer supported and the Java DSL requires that all URIs be absolute (less any context root).
|
||||||
|
At that time, Spring Security 7 will use `PathPatternRequestMatcher` by default.
|
||||||
|
|
||||||
|
To check how prepared you are for this change, you can publish this bean:
|
||||||
|
|
||||||
|
[tabs]
|
||||||
|
======
|
||||||
|
Java::
|
||||||
|
+
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() {
|
||||||
|
return new PathPatternRequestMatcherBuilderFactoryBean();
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
Kotlin::
|
||||||
|
+
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
fun requestMatcherBuilder(): PathPatternRequestMatcherBuilderFactoryBean {
|
||||||
|
return PathPatternRequestMatcherBuilderFactoryBean()
|
||||||
|
}
|
||||||
|
----
|
||||||
|
======
|
||||||
|
|
||||||
|
This will tell the Spring Security DSL to use `PathPatternRequestMatcher` for all request matchers that it constructs.
|
||||||
|
|
||||||
|
In the event that you are directly constructing an object (as opposed to having the DSL construct it) that has a `setRequestMatcher` method. you should also proactively specify a `PathPatternRequestMatcher` there as well.
|
||||||
|
|
||||||
|
For example, in the case of `LogoutFilter`, it constructs an `AntPathRequestMatcher` in Spring Security 6:
|
||||||
|
|
||||||
|
[method,java]
|
||||||
|
----
|
||||||
|
private RequestMatcher logoutUrl = new AntPathRequestMatcher("/logout");
|
||||||
|
----
|
||||||
|
|
||||||
|
and will change this to a `PathPatternRequestMatcher` in 7:
|
||||||
|
|
||||||
|
[method,java]
|
||||||
|
----
|
||||||
|
private RequestMatcher logoutUrl = PathPatternRequestMatcher.path().matcher("/logout");
|
||||||
|
----
|
||||||
|
|
||||||
|
If you are constructing your own `LogoutFilter`, consider calling `setLogoutRequestMatcher` to provide this `PathPatternRequestMatcher` in advance.
|
||||||
|
|
||||||
|
== Include the Servlet Path Prefix in Authorization Rules
|
||||||
|
|
||||||
|
For many applications <<use-path-pattern, the above>> will make no difference since most commonly all URIs listed are matched by the default servlet.
|
||||||
|
|
||||||
|
However, if you have other servlets with servlet path prefixes, xref:servlet/authorization/authorize-http-requests.adoc[then these paths now need to be supplied separately].
|
||||||
|
|
||||||
|
For example, if I have a Spring MVC controller with `@RequestMapping("/orders")` and my MVC application is deployed to `/mvc` (instead of the default servlet), then the URI for this endpoint is `/mvc/orders`.
|
||||||
|
Historically, the Java DSL hasn't had a simple way to specify the servlet path prefix and Spring Security attempted to infer it.
|
||||||
|
|
||||||
|
Over time, we learned that these inference would surprise developers.
|
||||||
|
Instead of taking this responsibility away from developers, now it is simpler to specify the servlet path prefix like so:
|
||||||
|
|
||||||
|
[method,java]
|
||||||
|
----
|
||||||
|
PathPatternRequestParser.Builder servlet = PathPatternRequestParser.servletPath("/mvc");
|
||||||
|
http
|
||||||
|
.authorizeHttpRequests((authorize) -> authorize
|
||||||
|
.requestMatchers(servlet.pattern("/orders/**").matcher()).authenticated()
|
||||||
|
)
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
For paths that belong to the default servlet, use `PathPatternRequestParser.path()` instead:
|
||||||
|
|
||||||
|
[method,java]
|
||||||
|
----
|
||||||
|
PathPatternRequestParser.Builder request = PathPatternRequestParser.path();
|
||||||
|
http
|
||||||
|
.authorizeHttpRequests((authorize) -> authorize
|
||||||
|
.requestMatchers(request.pattern("/js/**").matcher()).authenticated()
|
||||||
|
)
|
||||||
|
----
|
||||||
|
|
||||||
|
Note that this doesn't address every kind of servlet since not all servlets have a path prefix.
|
||||||
|
For example, expressions that match the JSP Servlet might use an ant pattern `/**/*.jsp`.
|
||||||
|
|
||||||
|
There is not yet a general-purpose replacement for these, and so you are encouraged to use `RegexRequestMatcher`, like so: `regexMatcher("\\.jsp$")`.
|
||||||
|
|
||||||
|
For many applications this will make no difference since most commonly all URIs listed are matched by the default servlet.
|
Loading…
x
Reference in New Issue
Block a user