Merge branch '5.8.x'
Closes gh-11347 in 6.0.x Closes gh-11945
This commit is contained in:
commit
ad2abd39dc
|
@ -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;
|
|||
*
|
||||
* @Bean
|
||||
* public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
* http.authorizeRequests().antMatchers("/**").hasRole("USER").and().formLogin();
|
||||
* http.authorizeHttpRequests().requestMatchers("/**").hasRole("USER").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>
|
||||
* @Configuration
|
||||
* @EnableWebSecurity
|
||||
* public class RequestMatchersSecurityConfig {
|
||||
*
|
||||
* @Bean
|
||||
* public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
* http
|
||||
* .securityMatchers((matchers) -> matchers
|
||||
* .requestMatchers("/api/**", "/oauth/**")
|
||||
* )
|
||||
* .authorizeHttpRequests((authorize) -> authorize
|
||||
* anyRequest().hasRole("USER")
|
||||
* )
|
||||
* .httpBasic(withDefaults());
|
||||
* return http.build();
|
||||
* }
|
||||
*
|
||||
* @Bean
|
||||
* public UserDetailsService userDetailsService() {
|
||||
* UserDetails user = User.withDefaultPasswordEncoder()
|
||||
* .username("user")
|
||||
* .password("password")
|
||||
* .roles("USER")
|
||||
* .build();
|
||||
* return new InMemoryUserDetailsManager(user);
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* The configuration below is the same as the previous configuration.
|
||||
*
|
||||
* <pre>
|
||||
* @Configuration
|
||||
* @EnableWebSecurity
|
||||
* public class RequestMatchersSecurityConfig {
|
||||
*
|
||||
* @Bean
|
||||
* public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
* http
|
||||
* .securityMatchers((matchers) -> matchers
|
||||
* .requestMatchers("/api/**")
|
||||
* .requestMatchers("/oauth/**")
|
||||
* )
|
||||
* .authorizeHttpRequests((authorize) -> authorize
|
||||
* anyRequest().hasRole("USER")
|
||||
* )
|
||||
* .httpBasic(withDefaults());
|
||||
* return http.build();
|
||||
* }
|
||||
*
|
||||
* @Bean
|
||||
* public UserDetailsService userDetailsService() {
|
||||
* UserDetails user = User.withDefaultPasswordEncoder()
|
||||
* .username("user")
|
||||
* .password("password")
|
||||
* .roles("USER")
|
||||
* .build();
|
||||
* return new InMemoryUserDetailsManager(user);
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* The configuration below is also the same as the above configuration.
|
||||
*
|
||||
* <pre>
|
||||
* @Configuration
|
||||
* @EnableWebSecurity
|
||||
* public class RequestMatchersSecurityConfig {
|
||||
*
|
||||
* @Bean
|
||||
* public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
* http
|
||||
* .securityMatchers((matchers) -> matchers
|
||||
* .requestMatchers("/api/**")
|
||||
* )
|
||||
* .securityMatchers((matchers) -> matchers
|
||||
* .requestMatchers("/oauth/**")
|
||||
* )
|
||||
* .authorizeHttpRequests((authorize) -> authorize
|
||||
* anyRequest().hasRole("USER")
|
||||
* )
|
||||
* .httpBasic(withDefaults());
|
||||
* return http.build();
|
||||
* }
|
||||
*
|
||||
* @Bean
|
||||
* public UserDetailsService userDetailsService() {
|
||||
* UserDetails user = User.withDefaultPasswordEncoder()
|
||||
* .username("user")
|
||||
* .password("password")
|
||||
* .roles("USER")
|
||||
* .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>
|
||||
* @Configuration
|
||||
* @EnableWebSecurity
|
||||
* public class RequestMatchersSecurityConfig {
|
||||
*
|
||||
* @Bean
|
||||
* public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
* http
|
||||
* .securityMatchers((matchers) -> matchers
|
||||
* .requestMatchers("/api/**", "/oauth/**")
|
||||
* )
|
||||
* .authorizeHttpRequests((authorize) -> authorize
|
||||
* .anyRequest().hasRole("USER")
|
||||
* )
|
||||
* .httpBasic(withDefaults());
|
||||
* return http.build();
|
||||
* }
|
||||
*
|
||||
* @Bean
|
||||
* public UserDetailsService userDetailsService() {
|
||||
* UserDetails user = User.withDefaultPasswordEncoder()
|
||||
* .username("user")
|
||||
* .password("password")
|
||||
* .roles("USER")
|
||||
* .build();
|
||||
* return new InMemoryUserDetailsManager(user);
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* The configuration below is the same as the previous configuration.
|
||||
*
|
||||
* <pre>
|
||||
* @Configuration
|
||||
* @EnableWebSecurity
|
||||
* public class RequestMatchersSecurityConfig {
|
||||
*
|
||||
* @Bean
|
||||
* public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
* http
|
||||
* .securityMatchers((matchers) -> matchers
|
||||
* .requestMatchers("/api/**")
|
||||
* .requestMatchers("/oauth/**")
|
||||
* )
|
||||
* .authorizeHttpRequests((authorize) -> authorize
|
||||
* .anyRequest().hasRole("USER")
|
||||
* )
|
||||
* .httpBasic(withDefaults());
|
||||
* return http.build();
|
||||
* }
|
||||
*
|
||||
* @Bean
|
||||
* public UserDetailsService userDetailsService() {
|
||||
* UserDetails user = User.withDefaultPasswordEncoder()
|
||||
* .username("user")
|
||||
* .password("password")
|
||||
* .roles("USER")
|
||||
* .build();
|
||||
* return new InMemoryUserDetailsManager(user);
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* The configuration below is also the same as the above configuration.
|
||||
*
|
||||
* <pre>
|
||||
* @Configuration
|
||||
* @EnableWebSecurity
|
||||
* public class RequestMatchersSecurityConfig {
|
||||
*
|
||||
* @Bean
|
||||
* public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
* http
|
||||
* .securityMatchers((matchers) -> matchers
|
||||
* .requestMatchers("/api/**")
|
||||
* )
|
||||
* .securityMatchers((matchers) -> matchers
|
||||
* .requestMatchers("/oauth/**")
|
||||
* )
|
||||
* .authorizeHttpRequests((authorize) -> authorize
|
||||
* .anyRequest().hasRole("USER")
|
||||
* )
|
||||
* .httpBasic(withDefaults());
|
||||
* return http.build();
|
||||
* }
|
||||
*
|
||||
* @Bean
|
||||
* public UserDetailsService userDetailsService() {
|
||||
* UserDetails user = User.withDefaultPasswordEncoder()
|
||||
* .username("user")
|
||||
* .password("password")
|
||||
* .roles("USER")
|
||||
* .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("/resources/**", "/static/**");
|
||||
* .requestMatchers("/resources/**", "/static/**");
|
||||
* </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("/resources/**").antMatchers("/static/**");
|
||||
* .requestMatchers("/resources/**").requestMatchers("/static/**");
|
||||
* </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("/resources/**");
|
||||
* .requestMatchers("/resources/**");
|
||||
* webSecurityBuilder.ignoring()
|
||||
* // ignore all URLs that start with /static/
|
||||
* .antMatchers("/static/**");
|
||||
* .requestMatchers("/static/**");
|
||||
* // 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("/resources/**");
|
||||
* .requestMatchers("/resources/**");
|
||||
* }
|
||||
*
|
||||
* @Bean
|
||||
* public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
* http.authorizeRequests().antMatchers("/public/**").permitAll().anyRequest()
|
||||
* http.authorizeRequests().requestMatchers("/public/**").permitAll().anyRequest()
|
||||
* .hasRole("USER").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")
|
||||
|
|
|
@ -109,13 +109,14 @@ SecurityFilterChain web(HttpSecurity http, AuthorizationManager<RequestAuthoriza
|
|||
|
||||
@Bean
|
||||
AuthorizationManager<RequestAuthorizationContext> requestMatcherAuthorizationManager(HandlerMappingIntrospector introspector) {
|
||||
MvcRequestMatcher.Builder mvcMatcherBuilder = new MvcRequestMatcher.Builder(introspector);
|
||||
RequestMatcher permitAll =
|
||||
new AndRequestMatcher(
|
||||
new MvcRequestMatcher(introspector, "/resources/**"),
|
||||
new MvcRequestMatcher(introspector, "/signup"),
|
||||
new MvcRequestMatcher(introspector, "/about"));
|
||||
RequestMatcher admin = new MvcRequestMatcher(introspector, "/admin/**");
|
||||
RequestMatcher db = new MvcRequestMatcher(introspector, "/db/**");
|
||||
mvcMatcherBuilder.pattern("/resources/**"),
|
||||
mvcMatcherBuilder.pattern("/signup"),
|
||||
mvcMatcherBuilder.pattern("/about"));
|
||||
RequestMatcher admin = mvcMatcherBuilder.pattern("/admin/**");
|
||||
RequestMatcher db = mvcMatcherBuilder.pattern("/db/**");
|
||||
RequestMatcher any = AnyRequestMatcher.INSTANCE;
|
||||
AuthorizationManager<HttpRequestServlet> manager = RequestMatcherDelegatingAuthorizationManager.builder()
|
||||
.add(permitAll, (context) -> new AuthorizationDecision(true))
|
||||
|
|
|
@ -144,7 +144,7 @@ You could then refer to the method as follows:
|
|||
----
|
||||
http
|
||||
.authorizeHttpRequests(authorize -> authorize
|
||||
.antMatchers("/user/**").access("@webSecurity.check(authentication,request)")
|
||||
.requestMatchers("/user/**").access("@webSecurity.check(authentication,request)")
|
||||
...
|
||||
)
|
||||
----
|
||||
|
@ -210,7 +210,7 @@ You could then refer to the method as follows:
|
|||
----
|
||||
http
|
||||
.authorizeHttpRequests(authorize -> authorize
|
||||
.antMatchers("/user/{userId}/**").access("@webSecurity.checkUserId(authentication,#userId)")
|
||||
.requestMatchers("/user/{userId}/**").access("@webSecurity.checkUserId(authentication,#userId)")
|
||||
...
|
||||
);
|
||||
----
|
||||
|
|
|
@ -151,8 +151,8 @@ To restrict access to this controller method to admin users, you can provide aut
|
|||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeHttpRequests(authorize -> authorize
|
||||
.antMatchers("/admin").hasRole("ADMIN")
|
||||
.authorizeHttpRequests((authorize) -> authorize
|
||||
.requestMatchers("/admin").hasRole("ADMIN")
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
|
@ -164,8 +164,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
|||
@Bean
|
||||
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
authorizeRequests {
|
||||
authorize(AntPathRequestMatcher("/admin"), hasRole("ADMIN"))
|
||||
authorizeHttpRequests {
|
||||
authorize("/admin", hasRole("ADMIN"))
|
||||
}
|
||||
}
|
||||
return http.build()
|
||||
|
@ -191,20 +191,24 @@ Additionally, depending on our Spring MVC configuration, the `/admin` URL also m
|
|||
The problem is that our security rule protects only `/admin`.
|
||||
We could add additional rules for all the permutations of Spring MVC, but this would be quite verbose and tedious.
|
||||
|
||||
Instead, we can use Spring Security's `MvcRequestMatcher`.
|
||||
The following configuration protects the same URLs that Spring MVC matches on by using Spring MVC to match on the URL.
|
||||
Fortunately, when using the `requestMatchers` DSL method, Spring Security automatically creates a `MvcRequestMatcher` if it detects that Spring MVC is available in the classpath.
|
||||
Therefore, it will protect the same URLs that Spring MVC will match on by using Spring MVC to match on the URL.
|
||||
|
||||
One common requirement when using Spring MVC is to specify the servlet path property, for that you can use the `MvcRequestMatcher.Builder` to create multiple `MvcRequestMatcher` instances that share the same servlet path:
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
public SecurityFilterChain filterChain(HttpSecurity http, HandlerMappingIntrospector introspector) throws Exception {
|
||||
MvcRequestMatcher.Builder mvcMatcherBuilder = new MvcRequestMatcher.Builder(introspector).servletPath("/path");
|
||||
http
|
||||
.authorizeHttpRequests(authorize -> authorize
|
||||
.mvcMatchers("/admin").hasRole("ADMIN")
|
||||
.authorizeHttpRequests((authorize) -> authorize
|
||||
.requestMatchers(mvcMatcherBuilder.pattern("/admin")).hasRole("ADMIN")
|
||||
.requestMatchers(mvcMatcherBuilder.pattern("/user")).hasRole("USER")
|
||||
);
|
||||
// ...
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
|
||||
|
@ -212,13 +216,15 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
|||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
open fun filterChain(http: HttpSecurity, introspector: HandlerMappingIntrospector): SecurityFilterChain {
|
||||
val mvcMatcherBuilder = MvcRequestMatcher.Builder(introspector)
|
||||
http {
|
||||
authorizeRequests {
|
||||
authorize("/admin", hasRole("ADMIN"))
|
||||
authorizeHttpRequests {
|
||||
authorize(mvcMatcherBuilder.pattern("/admin"), hasRole("ADMIN"))
|
||||
authorize(mvcMatcherBuilder.pattern("/user"), hasRole("USER"))
|
||||
}
|
||||
}
|
||||
// ...
|
||||
return http.build()
|
||||
}
|
||||
----
|
||||
====
|
||||
|
|
|
@ -584,7 +584,7 @@ public class WebSecurityConfig {
|
|||
http
|
||||
.csrf(csrf -> csrf
|
||||
// ignore our stomp endpoints since they are protected using Stomp headers
|
||||
.ignoringAntMatchers("/chat/**")
|
||||
.ignoringRequestMatchers("/chat/**")
|
||||
)
|
||||
.headers(headers -> headers
|
||||
// allow same origin to frame our site to support iframe SockJS
|
||||
|
@ -610,7 +610,7 @@ open class WebSecurityConfig {
|
|||
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
csrf {
|
||||
ignoringAntMatchers("/chat/**")
|
||||
ignoringRequestMatchers("/chat/**")
|
||||
}
|
||||
headers {
|
||||
frameOptions {
|
||||
|
|
|
@ -17,4 +17,4 @@ Reorganize imports
|
|||
Reorganize imports
|
||||
* https://github.com/spring-projects/spring-security/issues/11026[gh-11026] - Use `RequestAttributeSecurityContextRepository` instead of `NullSecurityContextRepository`
|
||||
* https://github.com/spring-projects/spring-security/pull/11887[gh-11827] - Change default authority for `oauth2Login()`
|
||||
* https://github.com/spring-projects/spring-security/issues/10347[gh-10347] - Remove `UsernamePasswordAuthenticationToken` check in `BasicAuthenticationFilter`
|
||||
* https://github.com/spring-projects/spring-security/issues/10347[gh-10347] - Remove `UsernamePasswordAuthenticationToken` check in `BasicAuthenticationFilter`
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2021 the original author or authors.
|
||||
* Copyright 2012-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.
|
||||
|
@ -172,4 +172,59 @@ public class MvcRequestMatcher implements RequestMatcher, RequestVariablesExtrac
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for {@link MvcRequestMatcher}
|
||||
*
|
||||
* @author Marcus Da Coregio
|
||||
* @since 5.8
|
||||
*/
|
||||
public static final class Builder {
|
||||
|
||||
private final HandlerMappingIntrospector introspector;
|
||||
|
||||
private String servletPath;
|
||||
|
||||
/**
|
||||
* Construct a new instance of this builder
|
||||
*/
|
||||
public Builder(HandlerMappingIntrospector introspector) {
|
||||
this.introspector = introspector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the servlet path to be used by the {@link MvcRequestMatcher} generated by
|
||||
* this builder
|
||||
* @param servletPath the servlet path to use
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder servletPath(String servletPath) {
|
||||
this.servletPath = servletPath;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link MvcRequestMatcher} that uses the provided pattern to match
|
||||
* @param pattern the pattern used to match
|
||||
* @return the generated {@link MvcRequestMatcher}
|
||||
*/
|
||||
public MvcRequestMatcher pattern(String pattern) {
|
||||
return pattern(null, pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link MvcRequestMatcher} that uses the provided pattern and HTTP
|
||||
* method to match
|
||||
* @param method the {@link HttpMethod}, can be null
|
||||
* @param pattern the patterns used to match
|
||||
* @return the generated {@link MvcRequestMatcher}
|
||||
*/
|
||||
public MvcRequestMatcher pattern(HttpMethod method, String pattern) {
|
||||
MvcRequestMatcher mvcRequestMatcher = new MvcRequestMatcher(this.introspector, pattern);
|
||||
mvcRequestMatcher.setServletPath(this.servletPath);
|
||||
mvcRequestMatcher.setMethod(method);
|
||||
return mvcRequestMatcher;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2021 the original author or authors.
|
||||
* Copyright 2012-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.
|
||||
|
@ -28,6 +28,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
|||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
|
||||
import org.springframework.web.servlet.handler.MatchableHandlerMapping;
|
||||
|
@ -245,4 +246,30 @@ public class MvcRequestMatcherTests {
|
|||
assertThat(this.matcher.matcher(this.request).isMatch()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builderWhenServletPathThenServletPathPresent() {
|
||||
MvcRequestMatcher matcher = new MvcRequestMatcher.Builder(this.introspector).servletPath("/path")
|
||||
.pattern("/endpoint");
|
||||
assertThat(matcher.getServletPath()).isEqualTo("/path");
|
||||
assertThat(ReflectionTestUtils.getField(matcher, "pattern")).isEqualTo("/endpoint");
|
||||
assertThat(ReflectionTestUtils.getField(matcher, "method")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builderWhenPatternThenPatternPresent() {
|
||||
MvcRequestMatcher matcher = new MvcRequestMatcher.Builder(this.introspector).pattern("/endpoint");
|
||||
assertThat(matcher.getServletPath()).isNull();
|
||||
assertThat(ReflectionTestUtils.getField(matcher, "pattern")).isEqualTo("/endpoint");
|
||||
assertThat(ReflectionTestUtils.getField(matcher, "method")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builderWhenMethodAndPatternThenMethodAndPatternPresent() {
|
||||
MvcRequestMatcher matcher = new MvcRequestMatcher.Builder(this.introspector).pattern(HttpMethod.GET,
|
||||
"/endpoint");
|
||||
assertThat(matcher.getServletPath()).isNull();
|
||||
assertThat(ReflectionTestUtils.getField(matcher, "pattern")).isEqualTo("/endpoint");
|
||||
assertThat(ReflectionTestUtils.getField(matcher, "method")).isEqualTo(HttpMethod.GET);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue