1
0
mirror of synced 2026-05-22 21:33:16 +00:00

Merge branch '5.8.x'

Closes gh-11347 in 6.0.x
Closes gh-11945
This commit is contained in:
Marcus Da Coregio
2022-10-03 16:02:18 -03:00
18 changed files with 1391 additions and 38 deletions
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -35,6 +35,7 @@ import org.springframework.security.web.util.matcher.DispatcherTypeRequestMatche
import org.springframework.security.web.util.matcher.RegexRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
/**
@@ -50,12 +51,21 @@ public abstract class AbstractRequestMatcherRegistry<C> {
private static final String HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "mvcHandlerMappingIntrospector";
private static final String HANDLER_MAPPING_INTROSPECTOR = "org.springframework.web.servlet.handler.HandlerMappingIntrospector";
private static final boolean mvcPresent;
private static final RequestMatcher ANY_REQUEST = AnyRequestMatcher.INSTANCE;
private ApplicationContext context;
private boolean anyRequestConfigured = false;
static {
mvcPresent = ClassUtils.isPresent(HANDLER_MAPPING_INTROSPECTOR,
AbstractRequestMatcherRegistry.class.getClassLoader());
}
protected final void setApplicationContext(ApplicationContext context) {
this.context = context;
}
@@ -85,7 +95,9 @@ public abstract class AbstractRequestMatcherRegistry<C> {
* instances.
* @param method the {@link HttpMethod} to use for any {@link HttpMethod}.
* @return the object that is chained after creating the {@link RequestMatcher}
* @deprecated use {@link #requestMatchers(HttpMethod)} instead
*/
@Deprecated
public C antMatchers(HttpMethod method) {
return antMatchers(method, "/**");
}
@@ -99,7 +111,9 @@ public abstract class AbstractRequestMatcherRegistry<C> {
* @param antPatterns the ant patterns to create. If {@code null} or empty, then
* matches on nothing.
* @return the object that is chained after creating the {@link RequestMatcher}
* @deprecated use {@link #requestMatchers(HttpMethod, String...)} instead
*/
@Deprecated
public C antMatchers(HttpMethod method, String... antPatterns) {
Assert.state(!this.anyRequestConfigured, "Can't configure antMatchers after anyRequest");
return chainRequestMatchers(RequestMatchers.antMatchers(method, antPatterns));
@@ -112,7 +126,9 @@ public abstract class AbstractRequestMatcherRegistry<C> {
* @param antPatterns the ant patterns to create
* {@link org.springframework.security.web.util.matcher.AntPathRequestMatcher} from
* @return the object that is chained after creating the {@link RequestMatcher}
* @deprecated use {@link #requestMatchers(String...)} instead
*/
@Deprecated
public C antMatchers(String... antPatterns) {
Assert.state(!this.anyRequestConfigured, "Can't configure antMatchers after anyRequest");
return chainRequestMatchers(RequestMatchers.antMatchers(antPatterns));
@@ -132,7 +148,9 @@ public abstract class AbstractRequestMatcherRegistry<C> {
* @param mvcPatterns the patterns to match on. The rules for matching are defined by
* Spring MVC
* @return the object that is chained after creating the {@link RequestMatcher}.
* @deprecated use {@link #requestMatchers(String...)} instead
*/
@Deprecated
public abstract C mvcMatchers(String... mvcPatterns);
/**
@@ -150,7 +168,9 @@ public abstract class AbstractRequestMatcherRegistry<C> {
* @param mvcPatterns the patterns to match on. The rules for matching are defined by
* Spring MVC
* @return the object that is chained after creating the {@link RequestMatcher}.
* @deprecated use {@link #requestMatchers(HttpMethod, String...)} instead
*/
@Deprecated
public abstract C mvcMatchers(HttpMethod method, String... mvcPatterns);
/**
@@ -190,7 +210,10 @@ public abstract class AbstractRequestMatcherRegistry<C> {
* @param regexPatterns the regular expressions to create
* {@link org.springframework.security.web.util.matcher.RegexRequestMatcher} from
* @return the object that is chained after creating the {@link RequestMatcher}
* @deprecated use {@link #requestMatchers(RequestMatcher...)} with a
* {@link RegexRequestMatcher} instead
*/
@Deprecated
public C regexMatchers(HttpMethod method, String... regexPatterns) {
Assert.state(!this.anyRequestConfigured, "Can't configure regexMatchers after anyRequest");
return chainRequestMatchers(RequestMatchers.regexMatchers(method, regexPatterns));
@@ -203,7 +226,10 @@ public abstract class AbstractRequestMatcherRegistry<C> {
* @param regexPatterns the regular expressions to create
* {@link org.springframework.security.web.util.matcher.RegexRequestMatcher} from
* @return the object that is chained after creating the {@link RequestMatcher}
* @deprecated use {@link #requestMatchers(RequestMatcher...)} with a
* {@link RegexRequestMatcher} instead
*/
@Deprecated
public C regexMatchers(String... regexPatterns) {
Assert.state(!this.anyRequestConfigured, "Can't configure regexMatchers after anyRequest");
return chainRequestMatchers(RequestMatchers.regexMatchers(regexPatterns));
@@ -250,6 +276,81 @@ public abstract class AbstractRequestMatcherRegistry<C> {
return chainRequestMatchers(Arrays.asList(requestMatchers));
}
/**
* <p>
* If the {@link HandlerMappingIntrospector} is available in the classpath, maps to an
* {@link MvcRequestMatcher} that also specifies a specific {@link HttpMethod} to
* match on. This matcher will use the same rules that Spring MVC uses for matching.
* For example, often times a mapping of the path "/path" will match on "/path",
* "/path/", "/path.html", etc. If the {@link HandlerMappingIntrospector} is not
* available, maps to an {@link AntPathRequestMatcher}.
* </p>
* <p>
* If a specific {@link RequestMatcher} must be specified, use
* {@link #requestMatchers(RequestMatcher...)} instead
* </p>
* @param method the {@link HttpMethod} to use or {@code null} for any
* {@link HttpMethod}.
* @param patterns the patterns to match on. The rules for matching are defined by
* Spring MVC if {@link MvcRequestMatcher} is used
* @return the object that is chained after creating the {@link RequestMatcher}.
* @since 5.8
*/
public C requestMatchers(HttpMethod method, String... patterns) {
List<RequestMatcher> matchers = new ArrayList<>();
if (mvcPresent) {
matchers.addAll(createMvcMatchers(method, patterns));
}
else {
matchers.addAll(RequestMatchers.antMatchers(method, patterns));
}
return requestMatchers(matchers.toArray(new RequestMatcher[0]));
}
/**
* <p>
* If the {@link HandlerMappingIntrospector} is available in the classpath, maps to an
* {@link MvcRequestMatcher} that does not care which {@link HttpMethod} is used. This
* matcher will use the same rules that Spring MVC uses for matching. For example,
* often times a mapping of the path "/path" will match on "/path", "/path/",
* "/path.html", etc. If the {@link HandlerMappingIntrospector} is not available, maps
* to an {@link AntPathRequestMatcher}.
* </p>
* <p>
* If a specific {@link RequestMatcher} must be specified, use
* {@link #requestMatchers(RequestMatcher...)} instead
* </p>
* @param patterns the patterns to match on. The rules for matching are defined by
* Spring MVC if {@link MvcRequestMatcher} is used
* @return the object that is chained after creating the {@link RequestMatcher}.
* @since 5.8
*/
public C requestMatchers(String... patterns) {
return requestMatchers(null, patterns);
}
/**
* <p>
* If the {@link HandlerMappingIntrospector} is available in the classpath, maps to an
* {@link MvcRequestMatcher} that matches on a specific {@link HttpMethod}. This
* matcher will use the same rules that Spring MVC uses for matching. For example,
* often times a mapping of the path "/path" will match on "/path", "/path/",
* "/path.html", etc. If the {@link HandlerMappingIntrospector} is not available, maps
* to an {@link AntPathRequestMatcher}.
* </p>
* <p>
* If a specific {@link RequestMatcher} must be specified, use
* {@link #requestMatchers(RequestMatcher...)} instead
* </p>
* @param method the {@link HttpMethod} to use or {@code null} for any
* {@link HttpMethod}.
* @return the object that is chained after creating the {@link RequestMatcher}.
* @since 5.8
*/
public C requestMatchers(HttpMethod method) {
return requestMatchers(method, "/**");
}
/**
* Subclasses should implement this method for returning the object that is chained to
* the creation of the {@link RequestMatcher} instances.
@@ -28,6 +28,7 @@ import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.core.OrderComparator;
import org.springframework.core.Ordered;
@@ -91,6 +92,7 @@ import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RegexRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
@@ -116,7 +118,7 @@ import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
*
* &#064;Bean
* public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
* http.authorizeRequests().antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;).and().formLogin();
* http.authorizeHttpRequests().requestMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;).and().formLogin();
* return http.build();
* }
*
@@ -140,6 +142,12 @@ import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
implements SecurityBuilder<DefaultSecurityFilterChain>, HttpSecurityBuilder<HttpSecurity> {
private static final String HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "mvcHandlerMappingIntrospector";
private static final String HANDLER_MAPPING_INTROSPECTOR = "org.springframework.web.servlet.handler.HandlerMappingIntrospector";
private static final boolean mvcPresent;
private final RequestMatcherConfigurer requestMatcherConfigurer;
private List<OrderedFilter> filters = new ArrayList<>();
@@ -150,6 +158,10 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
private AuthenticationManager authenticationManager;
static {
mvcPresent = ClassUtils.isPresent(HANDLER_MAPPING_INTROSPECTOR, HttpSecurity.class.getClassLoader());
}
/**
* Creates a new instance
* @param objectPostProcessor the {@link ObjectPostProcessor} that should be used
@@ -3193,7 +3205,9 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
* }
* </pre>
* @return the {@link RequestMatcherConfigurer} for further customizations
* @deprecated use {@link #securityMatchers()} instead
*/
@Deprecated
public RequestMatcherConfigurer requestMatchers() {
return this.requestMatcherConfigurer;
}
@@ -3325,7 +3339,9 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
* @param requestMatcherCustomizer the {@link Customizer} to provide more options for
* the {@link RequestMatcherConfigurer}
* @return the {@link HttpSecurity} for further customizations
* @deprecated use {@link #securityMatchers(Customizer)} instead
*/
@Deprecated
public HttpSecurity requestMatchers(Customizer<RequestMatcherConfigurer> requestMatcherCustomizer) {
requestMatcherCustomizer.customize(this.requestMatcherConfigurer);
return HttpSecurity.this;
@@ -3345,15 +3361,318 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
* @param requestMatcher the {@link RequestMatcher} to use (i.e. new
* AntPathRequestMatcher("/admin/**","GET") )
* @return the {@link HttpSecurity} for further customizations
* @deprecated use {@link #securityMatcher(RequestMatcher)} instead
* @see #requestMatchers()
* @see #antMatcher(String)
* @see #regexMatcher(String)
*/
@Deprecated
public HttpSecurity requestMatcher(RequestMatcher requestMatcher) {
this.requestMatcher = requestMatcher;
return this;
}
/**
* Allows specifying which {@link HttpServletRequest} instances this
* {@link HttpSecurity} will be invoked on. This method allows for easily invoking the
* {@link HttpSecurity} for multiple different {@link RequestMatcher} instances. If
* only a single {@link RequestMatcher} is necessary consider using
* {@link #securityMatcher(String)}, or {@link #securityMatcher(RequestMatcher)}.
*
* <p>
* Invoking {@link #securityMatchers()} will not override previous invocations of
* {@link #securityMatchers()}}, {@link #securityMatchers(Customizer)}
* {@link #securityMatcher(String)} and {@link #securityMatcher(RequestMatcher)}
* </p>
*
* <h3>Example Configurations</h3>
*
* The following configuration enables the {@link HttpSecurity} for URLs that begin
* with "/api/" or "/oauth/".
*
* <pre>
* &#064;Configuration
* &#064;EnableWebSecurity
* public class RequestMatchersSecurityConfig {
*
* &#064;Bean
* public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
* http
* .securityMatchers((matchers) -&gt; matchers
* .requestMatchers(&quot;/api/**&quot;, &quot;/oauth/**&quot;)
* )
* .authorizeHttpRequests((authorize) -&gt; authorize
* anyRequest().hasRole(&quot;USER&quot;)
* )
* .httpBasic(withDefaults());
* return http.build();
* }
*
* &#064;Bean
* public UserDetailsService userDetailsService() {
* UserDetails user = User.withDefaultPasswordEncoder()
* .username(&quot;user&quot;)
* .password(&quot;password&quot;)
* .roles(&quot;USER&quot;)
* .build();
* return new InMemoryUserDetailsManager(user);
* }
* }
* </pre>
*
* The configuration below is the same as the previous configuration.
*
* <pre>
* &#064;Configuration
* &#064;EnableWebSecurity
* public class RequestMatchersSecurityConfig {
*
* &#064;Bean
* public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
* http
* .securityMatchers((matchers) -&gt; matchers
* .requestMatchers(&quot;/api/**&quot;)
* .requestMatchers(&quot;/oauth/**&quot;)
* )
* .authorizeHttpRequests((authorize) -&gt; authorize
* anyRequest().hasRole(&quot;USER&quot;)
* )
* .httpBasic(withDefaults());
* return http.build();
* }
*
* &#064;Bean
* public UserDetailsService userDetailsService() {
* UserDetails user = User.withDefaultPasswordEncoder()
* .username(&quot;user&quot;)
* .password(&quot;password&quot;)
* .roles(&quot;USER&quot;)
* .build();
* return new InMemoryUserDetailsManager(user);
* }
* }
* </pre>
*
* The configuration below is also the same as the above configuration.
*
* <pre>
* &#064;Configuration
* &#064;EnableWebSecurity
* public class RequestMatchersSecurityConfig {
*
* &#064;Bean
* public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
* http
* .securityMatchers((matchers) -&gt; matchers
* .requestMatchers(&quot;/api/**&quot;)
* )
* .securityMatchers((matchers) -&gt; matchers
* .requestMatchers(&quot;/oauth/**&quot;)
* )
* .authorizeHttpRequests((authorize) -&gt; authorize
* anyRequest().hasRole(&quot;USER&quot;)
* )
* .httpBasic(withDefaults());
* return http.build();
* }
*
* &#064;Bean
* public UserDetailsService userDetailsService() {
* UserDetails user = User.withDefaultPasswordEncoder()
* .username(&quot;user&quot;)
* .password(&quot;password&quot;)
* .roles(&quot;USER&quot;)
* .build();
* return new InMemoryUserDetailsManager(user);
* }
* }
* </pre>
* @return the {@link RequestMatcherConfigurer} for further customizations
*/
public RequestMatcherConfigurer securityMatchers() {
return this.requestMatcherConfigurer;
}
/**
* Allows specifying which {@link HttpServletRequest} instances this
* {@link HttpSecurity} will be invoked on. This method allows for easily invoking the
* {@link HttpSecurity} for multiple different {@link RequestMatcher} instances. If
* only a single {@link RequestMatcher} is necessary consider using
* {@link #securityMatcher(String)}, or {@link #securityMatcher(RequestMatcher)}.
*
* <p>
* Invoking {@link #securityMatchers(Customizer)} will not override previous
* invocations of {@link #securityMatchers()}}, {@link #securityMatchers(Customizer)}
* {@link #securityMatcher(String)} and {@link #securityMatcher(RequestMatcher)}
* </p>
*
* <h3>Example Configurations</h3>
*
* The following configuration enables the {@link HttpSecurity} for URLs that begin
* with "/api/" or "/oauth/".
*
* <pre>
* &#064;Configuration
* &#064;EnableWebSecurity
* public class RequestMatchersSecurityConfig {
*
* &#064;Bean
* public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
* http
* .securityMatchers((matchers) -&gt; matchers
* .requestMatchers(&quot;/api/**&quot;, &quot;/oauth/**&quot;)
* )
* .authorizeHttpRequests((authorize) -&gt; authorize
* .anyRequest().hasRole(&quot;USER&quot;)
* )
* .httpBasic(withDefaults());
* return http.build();
* }
*
* &#064;Bean
* public UserDetailsService userDetailsService() {
* UserDetails user = User.withDefaultPasswordEncoder()
* .username(&quot;user&quot;)
* .password(&quot;password&quot;)
* .roles(&quot;USER&quot;)
* .build();
* return new InMemoryUserDetailsManager(user);
* }
* }
* </pre>
*
* The configuration below is the same as the previous configuration.
*
* <pre>
* &#064;Configuration
* &#064;EnableWebSecurity
* public class RequestMatchersSecurityConfig {
*
* &#064;Bean
* public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
* http
* .securityMatchers((matchers) -&gt; matchers
* .requestMatchers(&quot;/api/**&quot;)
* .requestMatchers(&quot;/oauth/**&quot;)
* )
* .authorizeHttpRequests((authorize) -&gt; authorize
* .anyRequest().hasRole(&quot;USER&quot;)
* )
* .httpBasic(withDefaults());
* return http.build();
* }
*
* &#064;Bean
* public UserDetailsService userDetailsService() {
* UserDetails user = User.withDefaultPasswordEncoder()
* .username(&quot;user&quot;)
* .password(&quot;password&quot;)
* .roles(&quot;USER&quot;)
* .build();
* return new InMemoryUserDetailsManager(user);
* }
* }
* </pre>
*
* The configuration below is also the same as the above configuration.
*
* <pre>
* &#064;Configuration
* &#064;EnableWebSecurity
* public class RequestMatchersSecurityConfig {
*
* &#064;Bean
* public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
* http
* .securityMatchers((matchers) -&gt; matchers
* .requestMatchers(&quot;/api/**&quot;)
* )
* .securityMatchers((matchers) -&gt; matchers
* .requestMatchers(&quot;/oauth/**&quot;)
* )
* .authorizeHttpRequests((authorize) -&gt; authorize
* .anyRequest().hasRole(&quot;USER&quot;)
* )
* .httpBasic(withDefaults());
* return http.build();
* }
*
* &#064;Bean
* public UserDetailsService userDetailsService() {
* UserDetails user = User.withDefaultPasswordEncoder()
* .username(&quot;user&quot;)
* .password(&quot;password&quot;)
* .roles(&quot;USER&quot;)
* .build();
* return new InMemoryUserDetailsManager(user);
* }
* }
* </pre>
* @param requestMatcherCustomizer the {@link Customizer} to provide more options for
* the {@link RequestMatcherConfigurer}
* @return the {@link HttpSecurity} for further customizations
*/
public HttpSecurity securityMatchers(Customizer<RequestMatcherConfigurer> requestMatcherCustomizer) {
requestMatcherCustomizer.customize(this.requestMatcherConfigurer);
return HttpSecurity.this;
}
/**
* Allows configuring the {@link HttpSecurity} to only be invoked when matching the
* provided {@link RequestMatcher}. If more advanced configuration is necessary,
* consider using {@link #securityMatchers(Customizer)} ()}.
*
* <p>
* Invoking {@link #securityMatcher(RequestMatcher)} will override previous
* invocations of {@link #requestMatchers()}, {@link #mvcMatcher(String)},
* {@link #antMatcher(String)}, {@link #regexMatcher(String)},
* {@link #requestMatcher(RequestMatcher)}, {@link #securityMatchers(Customizer)},
* {@link #securityMatchers()} and {@link #securityMatcher(String)}
* </p>
* @param requestMatcher the {@link RequestMatcher} to use (i.e. new
* AntPathRequestMatcher("/admin/**","GET") )
* @return the {@link HttpSecurity} for further customizations
* @see #securityMatcher(String)
*/
public HttpSecurity securityMatcher(RequestMatcher requestMatcher) {
this.requestMatcher = requestMatcher;
return this;
}
/**
* Allows configuring the {@link HttpSecurity} to only be invoked when matching the
* provided pattern. This method creates a {@link MvcRequestMatcher} if Spring MVC is
* in the classpath or creates an {@link AntPathRequestMatcher} if not. If more
* advanced configuration is necessary, consider using
* {@link #securityMatchers(Customizer)} or {@link #securityMatcher(RequestMatcher)}.
*
* <p>
* Invoking {@link #securityMatcher(String)} will override previous invocations of
* {@link #mvcMatcher(String)}}, {@link #requestMatchers()},
* {@link #antMatcher(String)}, {@link #regexMatcher(String)}, and
* {@link #requestMatcher(RequestMatcher)}.
* </p>
* @param pattern the pattern to match on (i.e. "/admin/**")
* @return the {@link HttpSecurity} for further customizations
* @see AntPathRequestMatcher
* @see MvcRequestMatcher
*/
public HttpSecurity securityMatcher(String pattern) {
if (!mvcPresent) {
this.requestMatcher = new AntPathRequestMatcher(pattern);
return this;
}
if (!getContext().containsBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)) {
throw new NoSuchBeanDefinitionException("A Bean named " + HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME
+ " of type " + HandlerMappingIntrospector.class.getName()
+ " is required to use MvcRequestMatcher. Please ensure Spring Security & Spring MVC are configured in a shared ApplicationContext.");
}
HandlerMappingIntrospector introspector = getContext().getBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME,
HandlerMappingIntrospector.class);
this.requestMatcher = new MvcRequestMatcher(introspector, pattern);
return this;
}
/**
* Allows configuring the {@link HttpSecurity} to only be invoked when matching the
* provided ant pattern. If more advanced configuration is necessary, consider using
@@ -3367,8 +3686,10 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
* </p>
* @param antPattern the Ant Pattern to match on (i.e. "/admin/**")
* @return the {@link HttpSecurity} for further customizations
* @deprecated use {@link #securityMatcher(String)} instead
* @see AntPathRequestMatcher
*/
@Deprecated
public HttpSecurity antMatcher(String antPattern) {
return requestMatcher(new AntPathRequestMatcher(antPattern));
}
@@ -3386,8 +3707,10 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
* </p>
* @param mvcPattern the Spring MVC Pattern to match on (i.e. "/admin/**")
* @return the {@link HttpSecurity} for further customizations
* @deprecated use {@link #securityMatcher(String)} instead
* @see MvcRequestMatcher
*/
@Deprecated
public HttpSecurity mvcMatcher(String mvcPattern) {
HandlerMappingIntrospector introspector = new HandlerMappingIntrospector();
introspector.setApplicationContext(getContext());
@@ -3408,8 +3731,10 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
* </p>
* @param pattern the Regular Expression to match on (i.e. "/admin/.+")
* @return the {@link HttpSecurity} for further customizations
* @see RegexRequestMatcher
* @deprecated use {@link #securityMatcher(RequestMatcher)} with a
* {@link RegexRequestMatcher} instead
*/
@Deprecated
public HttpSecurity regexMatcher(String pattern) {
return requestMatcher(new RegexRequestMatcher(pattern, null));
}
@@ -3480,14 +3805,22 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
setApplicationContext(context);
}
/**
* @deprecated use {@link #requestMatchers(HttpMethod, String...)} instead
*/
@Override
@Deprecated
public MvcMatchersRequestMatcherConfigurer mvcMatchers(HttpMethod method, String... mvcPatterns) {
List<MvcRequestMatcher> mvcMatchers = createMvcMatchers(method, mvcPatterns);
setMatchers(mvcMatchers);
return new MvcMatchersRequestMatcherConfigurer(getContext(), mvcMatchers, this.matchers);
}
/**
* @deprecated use {@link #requestMatchers(String...)} instead
*/
@Override
@Deprecated
public MvcMatchersRequestMatcherConfigurer mvcMatchers(String... patterns) {
return mvcMatchers(null, patterns);
}
@@ -3500,7 +3833,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
private void setMatchers(List<? extends RequestMatcher> requestMatchers) {
this.matchers.addAll(requestMatchers);
requestMatcher(new OrRequestMatcher(this.matchers));
securityMatcher(new OrRequestMatcher(this.matchers));
}
/**
@@ -135,7 +135,7 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
* <pre>
* webSecurityBuilder.ignoring()
* // ignore all URLs that start with /resources/ or /static/
* .antMatchers(&quot;/resources/**&quot;, &quot;/static/**&quot;);
* .requestMatchers(&quot;/resources/**&quot;, &quot;/static/**&quot;);
* </pre>
*
* Alternatively this will accomplish the same result:
@@ -143,7 +143,7 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
* <pre>
* webSecurityBuilder.ignoring()
* // ignore all URLs that start with /resources/ or /static/
* .antMatchers(&quot;/resources/**&quot;).antMatchers(&quot;/static/**&quot;);
* .requestMatchers(&quot;/resources/**&quot;).requestMatchers(&quot;/static/**&quot;);
* </pre>
*
* Multiple invocations of ignoring() are also additive, so the following is also
@@ -152,10 +152,10 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
* <pre>
* webSecurityBuilder.ignoring()
* // ignore all URLs that start with /resources/
* .antMatchers(&quot;/resources/**&quot;);
* .requestMatchers(&quot;/resources/**&quot;);
* webSecurityBuilder.ignoring()
* // ignore all URLs that start with /static/
* .antMatchers(&quot;/static/**&quot;);
* .requestMatchers(&quot;/static/**&quot;);
* // now both URLs that start with /resources/ and /static/ will be ignored
* </pre>
* @return the {@link IgnoredRequestConfigurer} to use for registering request that
@@ -380,7 +380,9 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
* {@link MvcRequestMatcher#setMethod(HttpMethod)}
*
* @author Rob Winch
* @deprecated use {@link MvcRequestMatcher.Builder} instead
*/
@Deprecated
public final class MvcMatchersIgnoredRequestConfigurer extends IgnoredRequestConfigurer {
private final List<MvcRequestMatcher> mvcMatchers;
@@ -412,14 +414,22 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
setApplicationContext(context);
}
/**
* @deprecated use {@link #requestMatchers(HttpMethod, String...)} instead
*/
@Override
@Deprecated
public MvcMatchersIgnoredRequestConfigurer mvcMatchers(HttpMethod method, String... mvcPatterns) {
List<MvcRequestMatcher> mvcMatchers = createMvcMatchers(method, mvcPatterns);
WebSecurity.this.ignoredRequests.addAll(mvcMatchers);
return new MvcMatchersIgnoredRequestConfigurer(getApplicationContext(), mvcMatchers);
}
/**
* @deprecated use {@link #requestMatchers(String...)} instead
*/
@Override
@Deprecated
public MvcMatchersIgnoredRequestConfigurer mvcMatchers(String... mvcPatterns) {
return mvcMatchers(null, mvcPatterns);
}
@@ -41,12 +41,12 @@ import org.springframework.security.web.SecurityFilterChain;
* public WebSecurityCustomizer webSecurityCustomizer() {
* return (web) -> web.ignoring()
* // Spring Security should completely ignore URLs starting with /resources/
* .antMatchers(&quot;/resources/**&quot;);
* .requestMatchers(&quot;/resources/**&quot;);
* }
*
* &#064;Bean
* public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
* http.authorizeRequests().antMatchers(&quot;/public/**&quot;).permitAll().anyRequest()
* http.authorizeRequests().requestMatchers(&quot;/public/**&quot;).permitAll().anyRequest()
* .hasRole(&quot;USER&quot;).and()
* // Possibly more configuration ...
* .formLogin() // enable form based log in
@@ -146,12 +146,20 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
return postProcess(this.managerBuilder.build());
}
/**
* @deprecated use {@link #requestMatchers(String...)} instead
*/
@Override
@Deprecated
public MvcMatchersAuthorizedUrl mvcMatchers(String... mvcPatterns) {
return mvcMatchers(null, mvcPatterns);
}
/**
* @deprecated use {@link #requestMatchers(HttpMethod, String...)} instead
*/
@Override
@Deprecated
public MvcMatchersAuthorizedUrl mvcMatchers(HttpMethod method, String... mvcPatterns) {
return new MvcMatchersAuthorizedUrl(createMvcMatchers(method, mvcPatterns));
}
@@ -202,7 +210,9 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
* configuring the {@link MvcRequestMatcher#setServletPath(String)}.
*
* @author Evgeniy Cheban
* @deprecated use {@link MvcRequestMatcher.Builder} instead
*/
@Deprecated
public final class MvcMatchersAuthorizedUrl extends AuthorizedUrl {
private MvcMatchersAuthorizedUrl(List<MvcRequestMatcher> matchers) {
@@ -163,7 +163,10 @@ public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
* </pre>
*
* @since 4.0
* @deprecated use {@link #ignoringRequestMatchers(RequestMatcher...)} with an
* {@link org.springframework.security.web.util.matcher.AntPathRequestMatcher} instead
*/
@Deprecated
public CsrfConfigurer<H> ignoringAntMatchers(String... antPatterns) {
return new IgnoreCsrfProtectionRegistry(this.context).antMatchers(antPatterns).and();
}
@@ -197,6 +200,35 @@ public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
return new IgnoreCsrfProtectionRegistry(this.context).requestMatchers(requestMatchers).and();
}
/**
* <p>
* Allows specifying {@link HttpServletRequest} that should not use CSRF Protection
* even if they match the {@link #requireCsrfProtectionMatcher(RequestMatcher)}.
* </p>
*
* <p>
* For example, the following configuration will ensure CSRF protection ignores:
* </p>
* <ul>
* <li>Any GET, HEAD, TRACE, OPTIONS (this is the default)</li>
* <li>We also explicitly state to ignore any request that starts with "/sockjs/"</li>
* </ul>
*
* <pre>
* http
* .csrf()
* .ignoringRequestMatchers("/sockjs/**")
* .and()
* ...
* </pre>
*
* @since 5.8
* @see AbstractRequestMatcherRegistry#requestMatchers(String...)
*/
public CsrfConfigurer<H> ignoringRequestMatchers(String... patterns) {
return new IgnoreCsrfProtectionRegistry(this.context).requestMatchers(patterns).and();
}
/**
* <p>
* Specify the {@link SessionAuthenticationStrategy} to use. The default is a
@@ -350,14 +382,22 @@ public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
setApplicationContext(context);
}
/**
* @deprecated use {@link #requestMatchers(HttpMethod, String...)} instead
*/
@Override
@Deprecated
public MvcMatchersIgnoreCsrfProtectionRegistry mvcMatchers(HttpMethod method, String... mvcPatterns) {
List<MvcRequestMatcher> mvcMatchers = createMvcMatchers(method, mvcPatterns);
CsrfConfigurer.this.ignoredCsrfProtectionMatchers.addAll(mvcMatchers);
return new MvcMatchersIgnoreCsrfProtectionRegistry(getApplicationContext(), mvcMatchers);
}
/**
* @deprecated use {@link #requestMatchers(String...)} instead
*/
@Override
@Deprecated
public MvcMatchersIgnoreCsrfProtectionRegistry mvcMatchers(String... mvcPatterns) {
return mvcMatchers(null, mvcPatterns);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 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.
@@ -42,6 +42,7 @@ class CsrfDsl {
private var ignoringAntMatchers: Array<out String>? = null
private var ignoringRequestMatchers: Array<out RequestMatcher>? = null
private var ignoringRequestMatchersPatterns: Array<out String>? = null
private var disabled = false
/**
@@ -66,6 +67,16 @@ class CsrfDsl {
ignoringRequestMatchers = requestMatchers
}
/**
* Allows specifying [HttpServletRequest]s that should not use CSRF Protection
* even if they match the [requireCsrfProtectionMatcher].
*
* @param patterns the patterns that should not use CSRF protection
*/
fun ignoringRequestMatchers(vararg patterns: String) {
ignoringRequestMatchersPatterns = patterns
}
/**
* Disable CSRF protection
*/
@@ -80,6 +91,7 @@ class CsrfDsl {
sessionAuthenticationStrategy?.also { csrf.sessionAuthenticationStrategy(sessionAuthenticationStrategy) }
ignoringAntMatchers?.also { csrf.ignoringAntMatchers(*ignoringAntMatchers!!) }
ignoringRequestMatchers?.also { csrf.ignoringRequestMatchers(*ignoringRequestMatchers!!) }
ignoringRequestMatchersPatterns?.also { csrf.ignoringRequestMatchers(*ignoringRequestMatchersPatterns!!) }
if (disabled) {
csrf.disable()
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2022 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.
@@ -16,19 +16,28 @@
package org.springframework.security.config.annotation.web;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.List;
import jakarta.servlet.DispatcherType;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.DispatcherTypeRequestMatcher;
import org.springframework.security.web.util.matcher.RegexRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link AbstractRequestMatcherRegistry}.
@@ -37,11 +46,21 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
public class AbstractRequestMatcherRegistryTests {
private static final ObjectPostProcessor<Object> NO_OP_OBJECT_POST_PROCESSOR = new ObjectPostProcessor<Object>() {
@Override
public <O> O postProcess(O object) {
return object;
}
};
private TestRequestMatcherRegistry matcherRegistry;
@BeforeEach
public void setUp() {
this.matcherRegistry = new TestRequestMatcherRegistry();
ApplicationContext context = mock(ApplicationContext.class);
given(context.getBean(ObjectPostProcessor.class)).willReturn(NO_OP_OBJECT_POST_PROCESSOR);
this.matcherRegistry.setApplicationContext(context);
}
@Test
@@ -93,6 +112,91 @@ public class AbstractRequestMatcherRegistryTests {
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(DispatcherTypeRequestMatcher.class);
}
@Test
public void requestMatchersWhenPatternAndMvcPresentThenReturnMvcRequestMatcherType() throws Exception {
mockMvcPresentClasspath(true);
mockMvcIntrospector(true);
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers("/path");
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers.size()).isEqualTo(1);
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(MvcRequestMatcher.class);
}
@Test
public void requestMatchersWhenHttpMethodAndPatternAndMvcPresentThenReturnMvcRequestMatcherType() throws Exception {
mockMvcPresentClasspath(true);
mockMvcIntrospector(true);
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers(HttpMethod.GET, "/path");
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers.size()).isEqualTo(1);
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(MvcRequestMatcher.class);
}
@Test
public void requestMatchersWhenHttpMethodAndMvcPresentThenReturnMvcRequestMatcherType() throws Exception {
mockMvcPresentClasspath(true);
mockMvcIntrospector(true);
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers(HttpMethod.GET);
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers.size()).isEqualTo(1);
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(MvcRequestMatcher.class);
}
@Test
public void requestMatchersWhenPatternAndMvcNotPresentThenReturnAntPathRequestMatcherType() throws Exception {
mockMvcPresentClasspath(false);
mockMvcIntrospector(false);
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers("/path");
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers.size()).isEqualTo(1);
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(AntPathRequestMatcher.class);
}
@Test
public void requestMatchersWhenHttpMethodAndPatternAndMvcNotPresentThenReturnAntPathRequestMatcherType()
throws Exception {
mockMvcPresentClasspath(false);
mockMvcIntrospector(false);
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers(HttpMethod.GET, "/path");
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers.size()).isEqualTo(1);
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(AntPathRequestMatcher.class);
}
@Test
public void requestMatchersWhenHttpMethodAndMvcNotPresentThenReturnAntPathMatcherType() throws Exception {
mockMvcPresentClasspath(false);
mockMvcIntrospector(false);
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers(HttpMethod.GET);
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers.size()).isEqualTo(1);
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(AntPathRequestMatcher.class);
}
@Test
public void requestMatchersWhenMvcPresentInClassPathAndMvcIntrospectorBeanNotAvailableThenException()
throws Exception {
mockMvcPresentClasspath(true);
mockMvcIntrospector(false);
assertThatExceptionOfType(NoSuchBeanDefinitionException.class)
.isThrownBy(() -> this.matcherRegistry.requestMatchers("/path")).withMessageContaining(
"Please ensure Spring Security & Spring MVC are configured in a shared ApplicationContext");
}
private void mockMvcIntrospector(boolean isPresent) {
ApplicationContext context = this.matcherRegistry.getApplicationContext();
given(context.containsBean("mvcHandlerMappingIntrospector")).willReturn(isPresent);
}
private void mockMvcPresentClasspath(Object newValue) throws Exception {
Field mvcPresentField = AbstractRequestMatcherRegistry.class.getDeclaredField("mvcPresent");
mvcPresentField.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(mvcPresentField, mvcPresentField.getModifiers() & ~Modifier.FINAL);
mvcPresentField.set(null, newValue);
}
private static class TestRequestMatcherRegistry extends AbstractRequestMatcherRegistry<List<RequestMatcher>> {
@Override
@@ -20,6 +20,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@@ -27,11 +28,13 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
@@ -80,6 +83,22 @@ public class CsrfConfigurerIgnoringRequestMatchersTests {
this.mvc.perform(put("/no-csrf")).andExpect(status().isOk());
}
@Test
public void requestWhenIgnoringRequestMatcherPatternThenIgnores() throws Exception {
this.spring.register(IgnoringPathsAndMatchersPatternConfig.class, BasicController.class).autowire();
this.mvc.perform(put("/csrf")).andExpect(status().isForbidden());
this.mvc.perform(post("/csrf")).andExpect(status().isForbidden());
this.mvc.perform(put("/no-csrf")).andExpect(status().isOk());
}
@Test
public void requestWhenIgnoringRequestMatcherPatternInLambdaThenIgnores() throws Exception {
this.spring.register(IgnoringPathsAndMatchersPatternInLambdaConfig.class, BasicController.class).autowire();
this.mvc.perform(put("/csrf")).andExpect(status().isForbidden());
this.mvc.perform(post("/csrf")).andExpect(status().isForbidden());
this.mvc.perform(put("/no-csrf")).andExpect(status().isOk());
}
@Configuration
@EnableWebSecurity
static class IgnoringRequestMatchers extends WebSecurityConfigurerAdapter {
@@ -156,6 +175,41 @@ public class CsrfConfigurerIgnoringRequestMatchersTests {
}
@Configuration
@EnableWebSecurity
@EnableWebMvc
static class IgnoringPathsAndMatchersPatternConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.csrf()
.ignoringRequestMatchers("/no-csrf");
// @formatter:on
return http.build();
}
}
@Configuration
@EnableWebSecurity
@EnableWebMvc
static class IgnoringPathsAndMatchersPatternInLambdaConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.csrf((csrf) -> csrf
.ignoringRequestMatchers("/no-csrf")
);
// @formatter:on
return http.build();
}
}
@RestController
public static class BasicController {
@@ -0,0 +1,567 @@
/*
* Copyright 2002-2022 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.configurers;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import javax.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletContext;
import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.security.config.Customizer.withDefaults;
/**
* @author Rob Winch
*
*/
public class HttpSecuritySecurityMatchersTests {
AnnotationConfigWebApplicationContext context;
MockHttpServletRequest request;
MockHttpServletResponse response;
MockFilterChain chain;
@Autowired
FilterChainProxy springSecurityFilterChain;
@BeforeEach
public void setup() throws Exception {
this.request = new MockHttpServletRequest("GET", "");
this.request.setMethod("GET");
this.response = new MockHttpServletResponse();
this.chain = new MockFilterChain();
mockMvcPresentClasspath(true);
}
@AfterEach
public void cleanup() {
if (this.context != null) {
this.context.close();
}
}
@Test
public void securityMatcherWhenMvcThenMvcMatcher() throws Exception {
loadConfig(SecurityMatcherMvcConfig.class, LegacyMvcMatchingConfig.class);
this.request.setServletPath("/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("/path.html");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("/path/");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
}
@Test
public void securityMatcherWhenNoMvcThenAntMatcher() throws Exception {
mockMvcPresentClasspath(false);
loadConfig(SecurityMatcherNoMvcConfig.class, LegacyMvcMatchingConfig.class);
this.request.setServletPath("/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("/path.html");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
setup();
this.request.setServletPath("/path/");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
}
@Test
public void securityMatcherWhenMvcMatcherAndGetFiltersNoUnsupportedMethodExceptionFromDummyRequest() {
loadConfig(SecurityMatcherMvcConfig.class);
assertThat(this.springSecurityFilterChain.getFilters("/path")).isNotEmpty();
}
@Test
public void securityMatchersWhenMvcThenMvcMatcher() throws Exception {
loadConfig(SecurityMatchersMvcMatcherConfig.class, LegacyMvcMatchingConfig.class);
this.request.setServletPath("/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("/path.html");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("/path/");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
}
@Test
public void securityMatchersWhenMvcMatcherInLambdaThenPathIsSecured() throws Exception {
loadConfig(SecurityMatchersMvcMatcherInLambdaConfig.class, LegacyMvcMatchingConfig.class);
this.request.setServletPath("/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("/path.html");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("/path/");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
}
@Test
public void securityMatchersMvcMatcherServletPath() throws Exception {
loadConfig(SecurityMatchersMvcMatcherServletPathConfig.class);
this.request.setServletPath("/spring");
this.request.setRequestURI("/spring/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("");
this.request.setRequestURI("/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
setup();
this.request.setServletPath("/other");
this.request.setRequestURI("/other/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
}
@Test
public void securityMatchersWhensMvcMatcherServletPathInLambdaThenPathIsSecured() throws Exception {
loadConfig(SecurityMatchersMvcMatcherServletPathInLambdaConfig.class);
this.request.setServletPath("/spring");
this.request.setRequestURI("/spring/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("");
this.request.setRequestURI("/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
setup();
this.request.setServletPath("/other");
this.request.setRequestURI("/other/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
}
@Test
public void securityMatchersWhenMultiMvcMatcherInLambdaThenAllPathsAreDenied() throws Exception {
loadConfig(MultiMvcMatcherInLambdaConfig.class);
this.request.setRequestURI("/test-1");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setRequestURI("/test-2");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setRequestURI("/test-3");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
}
@Test
public void securityMatchersWhenMultiMvcMatcherThenAllPathsAreDenied() throws Exception {
loadConfig(MultiMvcMatcherConfig.class);
this.request.setRequestURI("/test-1");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setRequestURI("/test-2");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setRequestURI("/test-3");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
}
public void loadConfig(Class<?>... configs) {
this.context = new AnnotationConfigWebApplicationContext();
this.context.register(configs);
this.context.setServletContext(new MockServletContext());
this.context.refresh();
this.context.getAutowireCapableBeanFactory().autowireBean(this);
}
private void mockMvcPresentClasspath(Object newValue) throws Exception {
mockMvcPresentClasspath(HttpSecurity.class, newValue);
mockMvcPresentClasspath(AbstractRequestMatcherRegistry.class, newValue);
}
private void mockMvcPresentClasspath(Class<?> clazz, Object newValue) throws Exception {
Field mvcPresentField = clazz.getDeclaredField("mvcPresent");
mvcPresentField.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(mvcPresentField, mvcPresentField.getModifiers() & ~Modifier.FINAL);
mvcPresentField.set(null, newValue);
}
@EnableWebSecurity
@Configuration
@EnableWebMvc
static class MultiMvcMatcherInLambdaConfig {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
SecurityFilterChain first(HttpSecurity http) throws Exception {
// @formatter:off
http
.securityMatchers((requests) -> requests
.requestMatchers("/test-1")
.requestMatchers("/test-2")
.requestMatchers("/test-3")
)
.authorizeHttpRequests((authorize) -> authorize.anyRequest().denyAll())
.httpBasic(withDefaults());
// @formatter:on
return http.build();
}
@Bean
SecurityFilterChain second(HttpSecurity http) throws Exception {
// @formatter:off
http
.securityMatchers((requests) -> requests
.requestMatchers("/test-1")
)
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().permitAll()
);
// @formatter:on
return http.build();
}
@RestController
static class PathController {
@RequestMapping({ "/test-1", "/test-2", "/test-3" })
String path() {
return "path";
}
}
}
@EnableWebSecurity
@Configuration
@EnableWebMvc
static class MultiMvcMatcherConfig {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
SecurityFilterChain first(HttpSecurity http) throws Exception {
// @formatter:off
http
.securityMatchers()
.requestMatchers("/test-1")
.requestMatchers("/test-2")
.requestMatchers("/test-3")
.and()
.authorizeHttpRequests()
.anyRequest().denyAll()
.and()
.httpBasic(withDefaults());
// @formatter:on
return http.build();
}
@Bean
SecurityFilterChain second(HttpSecurity http) throws Exception {
// @formatter:off
http
.securityMatchers()
.requestMatchers("/test-1")
.and()
.authorizeHttpRequests()
.anyRequest().permitAll();
// @formatter:on
return http.build();
}
@RestController
static class PathController {
@RequestMapping({ "/test-1", "/test-2", "/test-3" })
String path() {
return "path";
}
}
}
@EnableWebSecurity
@EnableWebMvc
@Configuration
@Import(UsersConfig.class)
static class SecurityMatcherMvcConfig {
@Bean
SecurityFilterChain appSecurity(HttpSecurity http) throws Exception {
// @formatter:off
http
.securityMatcher("/path")
.httpBasic().and()
.authorizeHttpRequests()
.anyRequest().denyAll();
// @formatter:on
return http.build();
}
@RestController
static class PathController {
@RequestMapping("/path")
String path() {
return "path";
}
}
}
@EnableWebSecurity
@Configuration
@Import(UsersConfig.class)
static class SecurityMatcherNoMvcConfig {
@Bean
SecurityFilterChain appSecurity(HttpSecurity http) throws Exception {
// @formatter:off
http
.securityMatcher("/path")
.httpBasic().and()
.authorizeHttpRequests()
.anyRequest().denyAll();
// @formatter:on
return http.build();
}
@RestController
static class PathController {
@RequestMapping("/path")
String path() {
return "path";
}
}
}
@EnableWebSecurity
@Configuration
@EnableWebMvc
@Import(UsersConfig.class)
static class SecurityMatchersMvcMatcherConfig {
@Bean
SecurityFilterChain appSecurity(HttpSecurity http) throws Exception {
// @formatter:off
http
.securityMatchers()
.requestMatchers("/path")
.and()
.httpBasic().and()
.authorizeHttpRequests()
.anyRequest().denyAll();
// @formatter:on
return http.build();
}
@RestController
static class PathController {
@RequestMapping("/path")
String path() {
return "path";
}
}
}
@EnableWebSecurity
@Configuration
@EnableWebMvc
static class SecurityMatchersMvcMatcherInLambdaConfig {
@Bean
SecurityFilterChain appSecurity(HttpSecurity http) throws Exception {
// @formatter:off
http
.securityMatchers((matchers) -> matchers
.requestMatchers("/path")
)
.httpBasic(withDefaults())
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().denyAll()
);
// @formatter:on
return http.build();
}
@RestController
static class PathController {
@RequestMapping("/path")
String path() {
return "path";
}
}
}
@EnableWebSecurity
@Configuration
@EnableWebMvc
@Import(UsersConfig.class)
static class SecurityMatchersMvcMatcherServletPathConfig {
@Bean
SecurityFilterChain appSecurity(HttpSecurity http, HandlerMappingIntrospector introspector) throws Exception {
MvcRequestMatcher.Builder mvcMatcherBuilder = new MvcRequestMatcher.Builder(introspector)
.servletPath("/spring");
// @formatter:off
http
.securityMatchers()
.requestMatchers(mvcMatcherBuilder.pattern("/path"))
.requestMatchers(mvcMatcherBuilder.pattern("/never-match"))
.and()
.httpBasic().and()
.authorizeHttpRequests()
.anyRequest().denyAll();
// @formatter:on
return http.build();
}
@RestController
static class PathController {
@RequestMapping("/path")
String path() {
return "path";
}
}
}
@EnableWebSecurity
@Configuration
@EnableWebMvc
@Import(UsersConfig.class)
static class SecurityMatchersMvcMatcherServletPathInLambdaConfig {
@Bean
SecurityFilterChain appSecurity(HttpSecurity http, HandlerMappingIntrospector introspector) throws Exception {
MvcRequestMatcher.Builder mvcMatcherBuilder = new MvcRequestMatcher.Builder(introspector)
.servletPath("/spring");
// @formatter:off
http
.securityMatchers((matchers) -> matchers
.requestMatchers(mvcMatcherBuilder.pattern("/path"))
.requestMatchers(mvcMatcherBuilder.pattern("/never-match"))
)
.httpBasic(withDefaults())
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().denyAll()
);
// @formatter:on
return http.build();
}
@RestController
static class PathController {
@RequestMapping("/path")
String path() {
return "path";
}
}
}
@Configuration
static class UsersConfig {
@Bean
UserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder().username("user").password("password").roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
}
@Configuration
static class LegacyMvcMatchingConfig implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseSuffixPatternMatch(true);
configurer.setUseTrailingSlashMatch(true);
}
}
}
@@ -44,6 +44,7 @@ import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.post
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.servlet.config.annotation.EnableWebMvc
/**
* Tests for [CsrfDsl]
@@ -279,6 +280,38 @@ class CsrfDslTests {
}
}
@Test
fun `CSRF when ignoring request matchers pattern then CSRF disabled on matching requests`() {
this.spring.register(IgnoringRequestMatchersPatternConfig::class.java, BasicController::class.java).autowire()
this.mockMvc.post("/test1")
.andExpect {
status { isForbidden() }
}
this.mockMvc.post("/test2")
.andExpect {
status { isOk() }
}
}
@Configuration
@EnableWebSecurity
@EnableWebMvc
open class IgnoringRequestMatchersPatternConfig {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
csrf {
requireCsrfProtectionMatcher = AntPathRequestMatcher("/**")
ignoringRequestMatchers("/test2")
}
}
return http.build()
}
}
@RestController
internal class BasicController {
@PostMapping("/test1")