parent
13bc70f693
commit
e4c13e3c0e
|
@ -25,6 +25,7 @@ import java.util.Map;
|
|||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.security.config.annotation.web.builders.WebSecurity;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.filter.DelegatingFilterProxy;
|
||||
|
@ -57,7 +58,7 @@ public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBui
|
|||
private final LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers = new LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>>();
|
||||
private final List<SecurityConfigurer<O, B>> configurersAddedInInitializing = new ArrayList<SecurityConfigurer<O, B>>();
|
||||
|
||||
private final Map<Class<Object>, Object> sharedObjects = new HashMap<Class<Object>, Object>();
|
||||
private final Map<Class<? extends Object>, Object> sharedObjects = new HashMap<Class<? extends Object>, Object>();
|
||||
|
||||
private final boolean allowConfigurersOfSameType;
|
||||
|
||||
|
@ -155,7 +156,7 @@ public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBui
|
|||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <C> void setSharedObject(Class<C> sharedType, C object) {
|
||||
this.sharedObjects.put((Class<Object>) sharedType, object);
|
||||
this.sharedObjects.put(sharedType, object);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -173,7 +174,7 @@ public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBui
|
|||
* Gets the shared objects
|
||||
* @return
|
||||
*/
|
||||
public Map<Class<Object>, Object> getSharedObjects() {
|
||||
public Map<Class<? extends Object>, Object> getSharedObjects() {
|
||||
return Collections.unmodifiableMap(this.sharedObjects);
|
||||
}
|
||||
|
||||
|
@ -300,7 +301,7 @@ public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBui
|
|||
* @return the possibly modified Object to use
|
||||
*/
|
||||
protected <P> P postProcess(P object) {
|
||||
return (P) this.objectPostProcessor.postProcess(object);
|
||||
return this.objectPostProcessor.postProcess(object);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -19,12 +19,15 @@ import java.util.ArrayList;
|
|||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractConfigAttributeRequestMatcherRegistry;
|
||||
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RegexRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
|
||||
|
||||
/**
|
||||
* A base class for registering {@link RequestMatcher}'s. For example, it might allow for
|
||||
|
@ -39,6 +42,12 @@ import org.springframework.security.web.util.matcher.RequestMatcher;
|
|||
public abstract class AbstractRequestMatcherRegistry<C> {
|
||||
private static final RequestMatcher ANY_REQUEST = AnyRequestMatcher.INSTANCE;
|
||||
|
||||
private ApplicationContext context;
|
||||
|
||||
protected final void setApplicationContext(ApplicationContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps any request.
|
||||
*
|
||||
|
@ -92,6 +101,57 @@ public abstract class AbstractRequestMatcherRegistry<C> {
|
|||
return chainRequestMatchers(RequestMatchers.antMatchers(antPatterns));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Maps 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.
|
||||
* </p>
|
||||
* <p>
|
||||
* If the current request will not be processed by Spring MVC, a reasonable default
|
||||
* using the pattern as a ant pattern will be used.
|
||||
* </p>
|
||||
*
|
||||
* @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}.
|
||||
*/
|
||||
public C mvcMatchers(String... mvcPatterns) {
|
||||
return mvcMatchers(null, mvcPatterns);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Maps 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.
|
||||
* </p>
|
||||
* <p>
|
||||
* If the current request will not be processed by Spring MVC, a reasonable default
|
||||
* using the pattern as a ant pattern will be used.
|
||||
* </p>
|
||||
*
|
||||
* @param method the HTTP method to match on
|
||||
* @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}.
|
||||
*/
|
||||
public C mvcMatchers(HttpMethod method, String... mvcPatterns) {
|
||||
HandlerMappingIntrospector introspector = new HandlerMappingIntrospector(
|
||||
this.context);
|
||||
List<RequestMatcher> matchers = new ArrayList<RequestMatcher>(mvcPatterns.length);
|
||||
for (String mvcPattern : mvcPatterns) {
|
||||
MvcRequestMatcher matcher = new MvcRequestMatcher(introspector, mvcPattern);
|
||||
if (method != null) {
|
||||
matcher.setMethod(method);
|
||||
}
|
||||
matchers.add(matcher);
|
||||
}
|
||||
return chainRequestMatchers(matchers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a {@link List} of
|
||||
* {@link org.springframework.security.web.util.matcher.RegexRequestMatcher}
|
||||
|
|
|
@ -23,6 +23,7 @@ import java.util.Map;
|
|||
import javax.servlet.Filter;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder;
|
||||
|
@ -62,6 +63,7 @@ import org.springframework.security.core.userdetails.UserDetailsService;
|
|||
import org.springframework.security.web.DefaultSecurityFilterChain;
|
||||
import org.springframework.security.web.PortMapper;
|
||||
import org.springframework.security.web.PortMapperImpl;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
|
||||
import org.springframework.security.web.session.HttpSessionEventPublisher;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
|
@ -113,7 +115,7 @@ public final class HttpSecurity extends
|
|||
AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
|
||||
implements SecurityBuilder<DefaultSecurityFilterChain>,
|
||||
HttpSecurityBuilder<HttpSecurity> {
|
||||
private final RequestMatcherConfigurer requestMatcherConfigurer = new RequestMatcherConfigurer();
|
||||
private final RequestMatcherConfigurer requestMatcherConfigurer;
|
||||
private List<Filter> filters = new ArrayList<Filter>();
|
||||
private RequestMatcher requestMatcher = AnyRequestMatcher.INSTANCE;
|
||||
private FilterComparator comparator = new FilterComparator();
|
||||
|
@ -126,15 +128,24 @@ public final class HttpSecurity extends
|
|||
* @param sharedObjects the shared Objects to initialize the {@link HttpSecurity} with
|
||||
* @see WebSecurityConfiguration
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public HttpSecurity(ObjectPostProcessor<Object> objectPostProcessor,
|
||||
AuthenticationManagerBuilder authenticationBuilder,
|
||||
Map<Class<Object>, Object> sharedObjects) {
|
||||
Map<Class<? extends Object>, Object> sharedObjects) {
|
||||
super(objectPostProcessor);
|
||||
Assert.notNull(authenticationBuilder, "authenticationBuilder cannot be null");
|
||||
setSharedObject(AuthenticationManagerBuilder.class, authenticationBuilder);
|
||||
for (Map.Entry<Class<Object>, Object> entry : sharedObjects.entrySet()) {
|
||||
setSharedObject(entry.getKey(), entry.getValue());
|
||||
for (Map.Entry<Class<? extends Object>, Object> entry : sharedObjects
|
||||
.entrySet()) {
|
||||
setSharedObject((Class<Object>) entry.getKey(), entry.getValue());
|
||||
}
|
||||
ApplicationContext context = (ApplicationContext) sharedObjects
|
||||
.get(ApplicationContext.class);
|
||||
this.requestMatcherConfigurer = new RequestMatcherConfigurer(context);
|
||||
}
|
||||
|
||||
private ApplicationContext getContext() {
|
||||
return getSharedObject(ApplicationContext.class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -634,7 +645,8 @@ public final class HttpSecurity extends
|
|||
*/
|
||||
public ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry authorizeRequests()
|
||||
throws Exception {
|
||||
return getOrApply(new ExpressionUrlAuthorizationConfigurer<HttpSecurity>())
|
||||
ApplicationContext context = getContext();
|
||||
return getOrApply(new ExpressionUrlAuthorizationConfigurer<HttpSecurity>(context))
|
||||
.getRegistry();
|
||||
}
|
||||
|
||||
|
@ -710,7 +722,8 @@ public final class HttpSecurity extends
|
|||
* @throws Exception
|
||||
*/
|
||||
public CsrfConfigurer<HttpSecurity> csrf() throws Exception {
|
||||
return getOrApply(new CsrfConfigurer<HttpSecurity>());
|
||||
ApplicationContext context = getContext();
|
||||
return getOrApply(new CsrfConfigurer<HttpSecurity>(context));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -917,7 +930,9 @@ public final class HttpSecurity extends
|
|||
*/
|
||||
public ChannelSecurityConfigurer<HttpSecurity>.ChannelRequestMatcherRegistry requiresChannel()
|
||||
throws Exception {
|
||||
return getOrApply(new ChannelSecurityConfigurer<HttpSecurity>()).getRegistry();
|
||||
ApplicationContext context = getContext();
|
||||
return getOrApply(new ChannelSecurityConfigurer<HttpSecurity>(context))
|
||||
.getRegistry();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1241,8 +1256,16 @@ public final class HttpSecurity extends
|
|||
*/
|
||||
public final class RequestMatcherConfigurer extends
|
||||
AbstractRequestMatcherRegistry<RequestMatcherConfigurer> {
|
||||
|
||||
private List<RequestMatcher> matchers = new ArrayList<RequestMatcher>();
|
||||
|
||||
/**
|
||||
* @param context
|
||||
*/
|
||||
private RequestMatcherConfigurer(ApplicationContext context) {
|
||||
setApplicationContext(context);
|
||||
}
|
||||
|
||||
protected RequestMatcherConfigurer chainRequestMatchers(
|
||||
List<RequestMatcher> requestMatchers) {
|
||||
matchers.addAll(requestMatchers);
|
||||
|
@ -1259,8 +1282,6 @@ public final class HttpSecurity extends
|
|||
return HttpSecurity.this;
|
||||
}
|
||||
|
||||
private RequestMatcherConfigurer() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -23,6 +23,7 @@ import javax.servlet.http.HttpServletRequest;
|
|||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
|
@ -80,7 +81,7 @@ public final class WebSecurity extends
|
|||
|
||||
private final List<SecurityBuilder<? extends SecurityFilterChain>> securityFilterChainBuilders = new ArrayList<SecurityBuilder<? extends SecurityFilterChain>>();
|
||||
|
||||
private final IgnoredRequestConfigurer ignoredRequestRegistry = new IgnoredRequestConfigurer();
|
||||
private IgnoredRequestConfigurer ignoredRequestRegistry;
|
||||
|
||||
private FilterSecurityInterceptor filterSecurityInterceptor;
|
||||
|
||||
|
@ -316,6 +317,10 @@ public final class WebSecurity extends
|
|||
public final class IgnoredRequestConfigurer extends
|
||||
AbstractRequestMatcherRegistry<IgnoredRequestConfigurer> {
|
||||
|
||||
private IgnoredRequestConfigurer(ApplicationContext context) {
|
||||
setApplicationContext(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IgnoredRequestConfigurer chainRequestMatchers(
|
||||
List<RequestMatcher> requestMatchers) {
|
||||
|
@ -329,13 +334,13 @@ public final class WebSecurity extends
|
|||
public WebSecurity and() {
|
||||
return WebSecurity.this;
|
||||
}
|
||||
|
||||
private IgnoredRequestConfigurer() {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext)
|
||||
throws BeansException {
|
||||
defaultWebSecurityExpressionHandler.setApplicationContext(applicationContext);
|
||||
this.defaultWebSecurityExpressionHandler
|
||||
.setApplicationContext(applicationContext);
|
||||
this.ignoredRequestRegistry = new IgnoredRequestConfigurer(applicationContext);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -329,6 +329,14 @@ public abstract class WebSecurityConfigurerAdapter implements
|
|||
}
|
||||
// @formatter:on
|
||||
|
||||
/**
|
||||
* Gets the ApplicationContext
|
||||
* @return the context
|
||||
*/
|
||||
protected final ApplicationContext getApplicationContext() {
|
||||
return this.context;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setApplicationContext(ApplicationContext context) {
|
||||
this.context = context;
|
||||
|
|
|
@ -20,6 +20,7 @@ import java.util.Collection;
|
|||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.security.access.SecurityConfig;
|
||||
import org.springframework.security.config.annotation.ObjectPostProcessor;
|
||||
|
@ -80,13 +81,14 @@ public final class ChannelSecurityConfigurer<H extends HttpSecurityBuilder<H>> e
|
|||
private LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = new LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>>();
|
||||
private List<ChannelProcessor> channelProcessors;
|
||||
|
||||
private final ChannelRequestMatcherRegistry REGISTRY = new ChannelRequestMatcherRegistry();
|
||||
private final ChannelRequestMatcherRegistry REGISTRY;
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
* @see HttpSecurity#requiresChannel()
|
||||
*/
|
||||
public ChannelSecurityConfigurer() {
|
||||
public ChannelSecurityConfigurer(ApplicationContext context) {
|
||||
this.REGISTRY = new ChannelRequestMatcherRegistry(context);
|
||||
}
|
||||
|
||||
public ChannelRequestMatcherRegistry getRegistry() {
|
||||
|
@ -146,6 +148,10 @@ public final class ChannelSecurityConfigurer<H extends HttpSecurityBuilder<H>> e
|
|||
public final class ChannelRequestMatcherRegistry extends
|
||||
AbstractConfigAttributeRequestMatcherRegistry<RequiresChannelUrl> {
|
||||
|
||||
private ChannelRequestMatcherRegistry(ApplicationContext context) {
|
||||
setApplicationContext(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RequiresChannelUrl chainRequestMatchersInternal(
|
||||
List<RequestMatcher> requestMatchers) {
|
||||
|
@ -185,9 +191,6 @@ public final class ChannelSecurityConfigurer<H extends HttpSecurityBuilder<H>> e
|
|||
public H and() {
|
||||
return ChannelSecurityConfigurer.this.and();
|
||||
}
|
||||
|
||||
private ChannelRequestMatcherRegistry() {
|
||||
}
|
||||
}
|
||||
|
||||
public final class RequiresChannelUrl {
|
||||
|
|
|
@ -21,6 +21,7 @@ import java.util.List;
|
|||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
|
||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
||||
|
@ -78,12 +79,14 @@ public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
|
|||
new HttpSessionCsrfTokenRepository());
|
||||
private RequestMatcher requireCsrfProtectionMatcher = CsrfFilter.DEFAULT_CSRF_MATCHER;
|
||||
private List<RequestMatcher> ignoredCsrfProtectionMatchers = new ArrayList<RequestMatcher>();
|
||||
private final ApplicationContext context;
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
* @see HttpSecurity#csrf()
|
||||
*/
|
||||
public CsrfConfigurer() {
|
||||
public CsrfConfigurer(ApplicationContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -141,7 +144,8 @@ public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
|
|||
* @since 4.0
|
||||
*/
|
||||
public CsrfConfigurer<H> ignoringAntMatchers(String... antPatterns) {
|
||||
return new IgnoreCsrfProtectionRegistry().antMatchers(antPatterns).and();
|
||||
return new IgnoreCsrfProtectionRegistry(this.context).antMatchers(antPatterns)
|
||||
.and();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
@ -265,6 +269,13 @@ public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
|
|||
private class IgnoreCsrfProtectionRegistry
|
||||
extends AbstractRequestMatcherRegistry<IgnoreCsrfProtectionRegistry> {
|
||||
|
||||
/**
|
||||
* @param context
|
||||
*/
|
||||
private IgnoreCsrfProtectionRegistry(ApplicationContext context) {
|
||||
setApplicationContext(context);
|
||||
}
|
||||
|
||||
public CsrfConfigurer<H> and() {
|
||||
return CsrfConfigurer.this;
|
||||
}
|
||||
|
|
|
@ -84,7 +84,7 @@ public final class ExpressionUrlAuthorizationConfigurer<H extends HttpSecurityBu
|
|||
private static final String fullyAuthenticated = "fullyAuthenticated";
|
||||
private static final String rememberMe = "rememberMe";
|
||||
|
||||
private final ExpressionInterceptUrlRegistry REGISTRY = new ExpressionInterceptUrlRegistry();
|
||||
private final ExpressionInterceptUrlRegistry REGISTRY;
|
||||
|
||||
private SecurityExpressionHandler<FilterInvocation> expressionHandler;
|
||||
|
||||
|
@ -92,7 +92,8 @@ public final class ExpressionUrlAuthorizationConfigurer<H extends HttpSecurityBu
|
|||
* Creates a new instance
|
||||
* @see HttpSecurity#authorizeRequests()
|
||||
*/
|
||||
public ExpressionUrlAuthorizationConfigurer() {
|
||||
public ExpressionUrlAuthorizationConfigurer(ApplicationContext context) {
|
||||
this.REGISTRY = new ExpressionInterceptUrlRegistry(context);
|
||||
}
|
||||
|
||||
public ExpressionInterceptUrlRegistry getRegistry() {
|
||||
|
@ -103,6 +104,13 @@ public final class ExpressionUrlAuthorizationConfigurer<H extends HttpSecurityBu
|
|||
extends
|
||||
ExpressionUrlAuthorizationConfigurer<H>.AbstractInterceptUrlRegistry<ExpressionInterceptUrlRegistry, AuthorizedUrl> {
|
||||
|
||||
/**
|
||||
* @param context
|
||||
*/
|
||||
private ExpressionInterceptUrlRegistry(ApplicationContext context) {
|
||||
setApplicationContext(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final AuthorizedUrl chainRequestMatchersInternal(
|
||||
List<RequestMatcher> requestMatchers) {
|
||||
|
|
|
@ -19,6 +19,7 @@ import java.util.ArrayList;
|
|||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.security.access.AccessDecisionManager;
|
||||
import org.springframework.security.access.AccessDecisionVoter;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
|
@ -86,7 +87,11 @@ import org.springframework.util.Assert;
|
|||
*/
|
||||
public final class UrlAuthorizationConfigurer<H extends HttpSecurityBuilder<H>> extends
|
||||
AbstractInterceptUrlConfigurer<UrlAuthorizationConfigurer<H>, H> {
|
||||
private final StandardInterceptUrlRegistry REGISTRY = new StandardInterceptUrlRegistry();
|
||||
private final StandardInterceptUrlRegistry REGISTRY;
|
||||
|
||||
public UrlAuthorizationConfigurer(ApplicationContext context) {
|
||||
this.REGISTRY = new StandardInterceptUrlRegistry(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* The StandardInterceptUrlRegistry is what users will interact with after applying
|
||||
|
@ -114,6 +119,13 @@ public final class UrlAuthorizationConfigurer<H extends HttpSecurityBuilder<H>>
|
|||
extends
|
||||
ExpressionUrlAuthorizationConfigurer<H>.AbstractInterceptUrlRegistry<StandardInterceptUrlRegistry, AuthorizedUrl> {
|
||||
|
||||
/**
|
||||
* @param context
|
||||
*/
|
||||
private StandardInterceptUrlRegistry(ApplicationContext context) {
|
||||
setApplicationContext(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final AuthorizedUrl chainRequestMatchersInternal(
|
||||
List<RequestMatcher> requestMatchers) {
|
||||
|
|
|
@ -45,7 +45,7 @@ public class FilterChainBeanDefinitionParser implements BeanDefinitionParser {
|
|||
|
||||
if (StringUtils.hasText(path)) {
|
||||
Assert.isTrue(!StringUtils.hasText(requestMatcher), "");
|
||||
builder.addConstructorArgValue(matcherType.createMatcher(path, null));
|
||||
builder.addConstructorArgValue(matcherType.createMatcher(pc, path, null));
|
||||
}
|
||||
else {
|
||||
Assert.isTrue(StringUtils.hasText(requestMatcher), "");
|
||||
|
|
|
@ -71,7 +71,7 @@ public class FilterChainMapBeanDefinitionDecorator implements BeanDefinitionDeco
|
|||
+ "'must not be empty", elt);
|
||||
}
|
||||
|
||||
BeanDefinition matcher = matcherType.createMatcher(path, null);
|
||||
BeanDefinition matcher = matcherType.createMatcher(parserContext, path, null);
|
||||
|
||||
if (filters.equals(HttpSecurityBeanDefinitionParser.OPT_FILTERS_NONE)) {
|
||||
securityFilterChains.add(createSecurityFilterChain(matcher,
|
||||
|
|
|
@ -165,7 +165,8 @@ public class FilterInvocationSecurityMetadataSourceParser implements BeanDefinit
|
|||
method = null;
|
||||
}
|
||||
|
||||
BeanDefinition matcher = matcherType.createMatcher(path, method);
|
||||
BeanDefinition matcher = matcherType.createMatcher(parserContext, path,
|
||||
method);
|
||||
BeanDefinitionBuilder attributeBuilder = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(SecurityConfig.class);
|
||||
|
||||
|
@ -194,7 +195,8 @@ public class FilterInvocationSecurityMetadataSourceParser implements BeanDefinit
|
|||
|
||||
if (addAuthenticatedAll && filterInvocationDefinitionMap.isEmpty()) {
|
||||
|
||||
BeanDefinition matcher = matcherType.createMatcher("/**", null);
|
||||
BeanDefinition matcher = matcherType.createMatcher(parserContext, "/**",
|
||||
null);
|
||||
BeanDefinitionBuilder attributeBuilder = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(SecurityConfig.class);
|
||||
attributeBuilder.addConstructorArgValue(new String[] { "authenticated" });
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright 2012-2016 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
|
||||
*
|
||||
* http://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.http;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
|
||||
|
||||
/**
|
||||
* Used for creating an instance of {@link HandlerMappingIntrospector} and autowiring the
|
||||
* {@link ApplicationContext}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 4.1.1
|
||||
*/
|
||||
class HandlerMappingIntrospectorFactoryBean implements ApplicationContextAware {
|
||||
|
||||
private ApplicationContext context;
|
||||
|
||||
HandlerMappingIntrospector createHandlerMappingIntrospector() {
|
||||
return new HandlerMappingIntrospector(this.context);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.
|
||||
* springframework.context.ApplicationContext)
|
||||
*/
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext)
|
||||
throws BeansException {
|
||||
this.context = applicationContext;
|
||||
}
|
||||
|
||||
}
|
|
@ -613,7 +613,7 @@ class HttpConfigurationBuilder {
|
|||
String requiredChannel = urlElt.getAttribute(ATT_REQUIRES_CHANNEL);
|
||||
|
||||
if (StringUtils.hasText(requiredChannel)) {
|
||||
BeanDefinition matcher = matcherType.createMatcher(path, method);
|
||||
BeanDefinition matcher = matcherType.createMatcher(pc, path, method);
|
||||
|
||||
RootBeanDefinition channelAttributes = new RootBeanDefinition(
|
||||
ChannelAttributeFactory.class);
|
||||
|
|
|
@ -198,7 +198,7 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
|
|||
|
||||
}
|
||||
else if (StringUtils.hasText(filterChainPattern)) {
|
||||
filterChainMatcher = MatcherType.fromElement(element).createMatcher(
|
||||
filterChainMatcher = MatcherType.fromElement(element).createMatcher(pc,
|
||||
filterChainPattern, null);
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -18,6 +18,8 @@ package org.springframework.security.config.http;
|
|||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RegexRequestMatcher;
|
||||
|
@ -33,17 +35,20 @@ import org.w3c.dom.Element;
|
|||
*/
|
||||
public enum MatcherType {
|
||||
ant(AntPathRequestMatcher.class), regex(RegexRequestMatcher.class), ciRegex(
|
||||
RegexRequestMatcher.class);
|
||||
RegexRequestMatcher.class), mvc(MvcRequestMatcher.class);
|
||||
|
||||
private static final String HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "org.springframework.web.servlet.handler.HandlerMappingIntrospector";
|
||||
private static final String HANDLER_MAPPING_INTROSPECTOR_FACTORY_BEAN_NAME = "org.springframework.security.config.http.HandlerMappingIntrospectorFactoryBean";
|
||||
|
||||
private static final String ATT_MATCHER_TYPE = "request-matcher";
|
||||
|
||||
private final Class<? extends RequestMatcher> type;
|
||||
final Class<? extends RequestMatcher> type;
|
||||
|
||||
MatcherType(Class<? extends RequestMatcher> type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public BeanDefinition createMatcher(String path, String method) {
|
||||
public BeanDefinition createMatcher(ParserContext pc, String path, String method) {
|
||||
if (("/**".equals(path) || "**".equals(path)) && method == null) {
|
||||
return new RootBeanDefinition(AnyRequestMatcher.class);
|
||||
}
|
||||
|
@ -51,8 +56,28 @@ public enum MatcherType {
|
|||
BeanDefinitionBuilder matcherBldr = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(type);
|
||||
|
||||
if (this == mvc) {
|
||||
if (!pc.getRegistry().isBeanNameInUse(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)) {
|
||||
BeanDefinitionBuilder hmifb = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(HandlerMappingIntrospectorFactoryBean.class);
|
||||
pc.getRegistry().registerBeanDefinition(HANDLER_MAPPING_INTROSPECTOR_FACTORY_BEAN_NAME,
|
||||
hmifb.getBeanDefinition());
|
||||
|
||||
RootBeanDefinition hmi = new RootBeanDefinition(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME);
|
||||
hmi.setFactoryBeanName(HANDLER_MAPPING_INTROSPECTOR_FACTORY_BEAN_NAME);
|
||||
hmi.setFactoryMethodName("createHandlerMappingIntrospector");
|
||||
pc.getRegistry().registerBeanDefinition(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME, hmi);
|
||||
}
|
||||
matcherBldr.addConstructorArgReference(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME);
|
||||
}
|
||||
|
||||
matcherBldr.addConstructorArgValue(path);
|
||||
matcherBldr.addConstructorArgValue(method);
|
||||
if (this == mvc) {
|
||||
matcherBldr.addPropertyValue("method", method);
|
||||
}
|
||||
else {
|
||||
matcherBldr.addConstructorArgValue(method);
|
||||
}
|
||||
|
||||
if (this == ciRegex) {
|
||||
matcherBldr.addConstructorArgValue(true);
|
||||
|
|
|
@ -12,8 +12,8 @@ base64 =
|
|||
## Whether a string should be base64 encoded
|
||||
attribute base64 {xsd:boolean}
|
||||
request-matcher =
|
||||
## Defines the strategy use for matching incoming requests. Currently the options are 'ant' (for ant path patterns), 'regex' for regular expressions and 'ciRegex' for case-insensitive regular expressions.
|
||||
attribute request-matcher {"ant" | "regex" | "ciRegex"}
|
||||
## Defines the strategy use for matching incoming requests. Currently the options are 'mvc' (for Spring MVC matcher), 'ant' (for ant path patterns), 'regex' for regular expressions and 'ciRegex' for case-insensitive regular expressions.
|
||||
attribute request-matcher {"mvc" | "ant" | "regex" | "ciRegex"}
|
||||
port =
|
||||
## Specifies an IP port number. Used to configure an embedded LDAP server, for example.
|
||||
attribute port { xsd:positiveInteger }
|
||||
|
|
|
@ -34,13 +34,14 @@
|
|||
<xs:attributeGroup name="request-matcher">
|
||||
<xs:attribute name="request-matcher" use="required">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'ant'
|
||||
(for ant path patterns), 'regex' for regular expressions and 'ciRegex' for
|
||||
case-insensitive regular expressions.
|
||||
<xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'mvc'
|
||||
(for Spring MVC matcher), 'ant' (for ant path patterns), 'regex' for regular expressions
|
||||
and 'ciRegex' for case-insensitive regular expressions.
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:token">
|
||||
<xs:enumeration value="mvc"/>
|
||||
<xs:enumeration value="ant"/>
|
||||
<xs:enumeration value="regex"/>
|
||||
<xs:enumeration value="ciRegex"/>
|
||||
|
@ -1187,13 +1188,14 @@
|
|||
</xs:attribute>
|
||||
<xs:attribute name="request-matcher">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'ant'
|
||||
(for ant path patterns), 'regex' for regular expressions and 'ciRegex' for
|
||||
case-insensitive regular expressions.
|
||||
<xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'mvc'
|
||||
(for Spring MVC matcher), 'ant' (for ant path patterns), 'regex' for regular expressions
|
||||
and 'ciRegex' for case-insensitive regular expressions.
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:token">
|
||||
<xs:enumeration value="mvc"/>
|
||||
<xs:enumeration value="ant"/>
|
||||
<xs:enumeration value="regex"/>
|
||||
<xs:enumeration value="ciRegex"/>
|
||||
|
@ -1557,13 +1559,14 @@
|
|||
<xs:attributeGroup name="filter-chain-map.attlist">
|
||||
<xs:attribute name="request-matcher">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'ant'
|
||||
(for ant path patterns), 'regex' for regular expressions and 'ciRegex' for
|
||||
case-insensitive regular expressions.
|
||||
<xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'mvc'
|
||||
(for Spring MVC matcher), 'ant' (for ant path patterns), 'regex' for regular expressions
|
||||
and 'ciRegex' for case-insensitive regular expressions.
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:token">
|
||||
<xs:enumeration value="mvc"/>
|
||||
<xs:enumeration value="ant"/>
|
||||
<xs:enumeration value="regex"/>
|
||||
<xs:enumeration value="ciRegex"/>
|
||||
|
@ -1668,13 +1671,14 @@
|
|||
</xs:attribute>
|
||||
<xs:attribute name="request-matcher">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'ant'
|
||||
(for ant path patterns), 'regex' for regular expressions and 'ciRegex' for
|
||||
case-insensitive regular expressions.
|
||||
<xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'mvc'
|
||||
(for Spring MVC matcher), 'ant' (for ant path patterns), 'regex' for regular expressions
|
||||
and 'ciRegex' for case-insensitive regular expressions.
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:token">
|
||||
<xs:enumeration value="mvc"/>
|
||||
<xs:enumeration value="ant"/>
|
||||
<xs:enumeration value="regex"/>
|
||||
<xs:enumeration value="ciRegex"/>
|
||||
|
|
|
@ -25,7 +25,7 @@ public class DisableUseExpressionsConfig extends BaseWebConfig {
|
|||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// This config is also on UrlAuthorizationConfigurer javadoc
|
||||
http
|
||||
.apply(new UrlAuthorizationConfigurer<HttpSecurity>()).getRegistry()
|
||||
.apply(new UrlAuthorizationConfigurer<HttpSecurity>(getApplicationContext())).getRegistry()
|
||||
.antMatchers("/users**","/sessions/**").hasRole("USER")
|
||||
.antMatchers("/signup").hasRole("ANONYMOUS")
|
||||
.anyRequest().hasRole("USER");
|
||||
|
|
|
@ -24,6 +24,8 @@ import org.springframework.mock.web.MockHttpServletResponse
|
|||
import org.springframework.security.access.SecurityConfig
|
||||
import org.springframework.security.crypto.codec.Base64
|
||||
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -197,6 +199,73 @@ class InterceptUrlConfigTests extends AbstractHttpConfigTests {
|
|||
response.status == HttpServletResponse.SC_FORBIDDEN
|
||||
}
|
||||
|
||||
def "intercept-url supports mvc matchers"() {
|
||||
setup:
|
||||
MockHttpServletRequest request = new MockHttpServletRequest(method:'GET')
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
MockFilterChain chain = new MockFilterChain()
|
||||
xml.http('request-matcher':'mvc') {
|
||||
'http-basic'()
|
||||
'intercept-url'(pattern: '/path', access: "denyAll")
|
||||
}
|
||||
bean('pathController',PathController)
|
||||
xml.'mvc:annotation-driven'()
|
||||
|
||||
createAppContext()
|
||||
when:
|
||||
request.servletPath = "/path"
|
||||
springSecurityFilterChain.doFilter(request, response, chain)
|
||||
then:
|
||||
response.status == HttpServletResponse.SC_UNAUTHORIZED
|
||||
when:
|
||||
request = new MockHttpServletRequest(method:'GET')
|
||||
response = new MockHttpServletResponse()
|
||||
chain = new MockFilterChain()
|
||||
request.servletPath = "/path.html"
|
||||
springSecurityFilterChain.doFilter(request, response, chain)
|
||||
then:
|
||||
response.status == HttpServletResponse.SC_UNAUTHORIZED
|
||||
when:
|
||||
request = new MockHttpServletRequest(method:'GET')
|
||||
response = new MockHttpServletResponse()
|
||||
chain = new MockFilterChain()
|
||||
request.servletPath = "/path/"
|
||||
springSecurityFilterChain.doFilter(request, response, chain)
|
||||
then:
|
||||
response.status == HttpServletResponse.SC_UNAUTHORIZED
|
||||
}
|
||||
|
||||
def "intercept-url mvc supports path variables"() {
|
||||
setup:
|
||||
MockHttpServletRequest request = new MockHttpServletRequest(method:'GET')
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
MockFilterChain chain = new MockFilterChain()
|
||||
xml.http('request-matcher':'mvc') {
|
||||
'http-basic'()
|
||||
'intercept-url'(pattern: '/user/{un}/**', access: "#un == 'user'")
|
||||
}
|
||||
createAppContext()
|
||||
when: 'user can access'
|
||||
request.servletPath = '/user/user/abc'
|
||||
springSecurityFilterChain.doFilter(request,response,chain)
|
||||
then: 'The response is OK'
|
||||
response.status == HttpServletResponse.SC_OK
|
||||
when: 'cannot access otheruser'
|
||||
request = new MockHttpServletRequest(method:'GET', servletPath : '/user/otheruser/abc')
|
||||
login(request, 'user', 'password')
|
||||
chain.reset()
|
||||
springSecurityFilterChain.doFilter(request,response,chain)
|
||||
then: 'The response is OK'
|
||||
response.status == HttpServletResponse.SC_FORBIDDEN
|
||||
when: 'user can access case insensitive URL'
|
||||
request = new MockHttpServletRequest(method:'GET', servletPath : '/USER/user/abc')
|
||||
login(request, 'user', 'password')
|
||||
chain.reset()
|
||||
springSecurityFilterChain.doFilter(request,response,chain)
|
||||
then: 'The response is OK'
|
||||
response.status == HttpServletResponse.SC_FORBIDDEN
|
||||
}
|
||||
|
||||
public static class Id {
|
||||
public boolean isOne(int i) {
|
||||
return i == 1;
|
||||
|
@ -207,4 +276,12 @@ class InterceptUrlConfigTests extends AbstractHttpConfigTests {
|
|||
String toEncode = username + ':' + password
|
||||
request.addHeader('Authorization','Basic ' + new String(Base64.encode(toEncode.getBytes('UTF-8'))))
|
||||
}
|
||||
|
||||
@RestController
|
||||
static class PathController {
|
||||
@RequestMapping("/path")
|
||||
public String path() {
|
||||
return "path";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -28,6 +28,7 @@ import org.springframework.http.HttpMethod;
|
|||
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.access.hierarchicalroles.RoleHierarchy;
|
||||
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
|
@ -41,7 +42,10 @@ import org.springframework.security.core.context.SecurityContextImpl;
|
|||
import org.springframework.security.web.FilterChainProxy;
|
||||
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
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 static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
|
@ -249,9 +253,117 @@ public class AuthorizeRequestsTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mvcMatcher() throws Exception {
|
||||
loadConfig(MvcMatcherConfig.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);
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
static class MvcMatcherConfig extends WebSecurityConfigurerAdapter {
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.httpBasic().and()
|
||||
.authorizeRequests()
|
||||
.mvcMatchers("/path").denyAll();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
|
||||
// @formatter:off
|
||||
auth
|
||||
.inMemoryAuthentication();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@RestController
|
||||
static class PathController {
|
||||
@RequestMapping("/path")
|
||||
public String path() {
|
||||
return "path";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mvcMatcherPathVariables() throws Exception {
|
||||
loadConfig(MvcMatcherPathVariablesConfig.class);
|
||||
|
||||
this.request.setServletPath("/user/user");
|
||||
|
||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
|
||||
|
||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
|
||||
|
||||
this.setup();
|
||||
this.request.setServletPath("/user/deny");
|
||||
|
||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
|
||||
|
||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
static class MvcMatcherPathVariablesConfig extends WebSecurityConfigurerAdapter {
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.httpBasic().and()
|
||||
.authorizeRequests()
|
||||
.mvcMatchers("/user/{userName}").access("#userName == 'user'");
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
|
||||
// @formatter:off
|
||||
auth
|
||||
.inMemoryAuthentication();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@RestController
|
||||
static class PathController {
|
||||
@RequestMapping("/path")
|
||||
public String path() {
|
||||
return "path";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
|
@ -389,6 +389,7 @@ Here is the list of improvements:
|
|||
* Ability to add a `Filter` at a specific location in the chain using `HttpSecurity.addFilterAt`
|
||||
|
||||
=== Web Application Security Improvements
|
||||
* <<mvc-requestmatcher,MvcRequestMatcher>>
|
||||
* <<headers-csp,Content Security Policy (CSP)>>
|
||||
* <<headers-hpkp,HTTP Public Key Pinning (HPKP)>>
|
||||
* <<cors,CORS>>
|
||||
|
@ -6726,6 +6727,77 @@ To enable Spring Security integration with Spring MVC add the `@EnableWebSecurit
|
|||
|
||||
NOTE: Spring Security provides the configuration using Spring MVC's http://docs.spring.io/spring-framework/docs/4.1.x/spring-framework-reference/htmlsingle/#mvc-config-customize[WebMvcConfigurerAdapter]. This means that if you are using more advanced options, like integrating with `WebMvcConfigurationSupport` directly, then you will need to manually provide the Spring Security configuration.
|
||||
|
||||
[[mvc-requestmatcher]]
|
||||
=== MvcRequestMatcher
|
||||
|
||||
Spring Security provides deep integration with how Spring MVC matches on URLs with `MvcRequestMatcher`.
|
||||
This is helpful to ensure your Security rules match the logic used to handle your requests.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
It is always recommended to provide authorization rules by matching on the `HttpServletRequest` and method security.
|
||||
|
||||
Providing authorization rules by matching on `HttpServletRequest` is good because it happens very early in the code path and helps reduce the https://en.wikipedia.org/wiki/Attack_surface[attack surface].
|
||||
Method security ensures that if someone has bypassed the web authorization rules, that your application is still secured.
|
||||
This is what is known as https://en.wikipedia.org/wiki/Defense_in_depth_(computing)[Defence in Depth]
|
||||
====
|
||||
|
||||
Consider a controller that is mapped as follows:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@RequestMapping("/admin")
|
||||
public String admin() {
|
||||
----
|
||||
|
||||
If we wanted to restrict access to this controller method to admin users, a developer can provide authorization rules by matching on the `HttpServletRequest` with the following:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
protected configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests()
|
||||
.antMatchers("/admin").hasRole("ADMIN");
|
||||
}
|
||||
----
|
||||
|
||||
or in XML
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<http>
|
||||
<intercept-url pattern="/admin" access="hasRole('ADMIN')"/>
|
||||
</http>
|
||||
----
|
||||
|
||||
With either configuration, the URL `/admin` will require the authenticated user to be an admin user.
|
||||
However, depending on our Spring MVC configuration, the URL `/admin.html` will also map to our `admin()` method.
|
||||
Additionally, depending on our Spring MVC configuration, the URL `/admin/` will also map to our `admin()` method.
|
||||
|
||||
The problem is that our security rule is only protecting `/admin`.
|
||||
We could add additional rules for all the permutations of Spring MVC, but this would be quite verbose and tedious.
|
||||
|
||||
Instead, we can leverage Spring Security's `MvcRequestMatcher`.
|
||||
The following configuration will protect the same URLs that Spring MVC will match on by using Spring MVC to match on the URL.
|
||||
|
||||
|
||||
[source,java]
|
||||
----
|
||||
protected configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests()
|
||||
.mvcMatchers("/admin").hasRole("ADMIN");
|
||||
}
|
||||
----
|
||||
|
||||
or in XML
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<http request-matcher="mvc">
|
||||
<intercept-url pattern="/admin" access="hasRole('ADMIN')"/>
|
||||
</http>
|
||||
----
|
||||
|
||||
[[mvc-authentication-principal]]
|
||||
=== @AuthenticationPrincipal
|
||||
|
@ -7403,7 +7475,7 @@ Sets the realm name used for basic authentication (if enabled). Corresponds to t
|
|||
|
||||
[[nsa-http-request-matcher]]
|
||||
* **request-matcher**
|
||||
Defines the `RequestMatcher` strategy used in the `FilterChainProxy` and the beans created by the `intercept-url` to match incoming requests. Options are currently `ant`, `regex` and `ciRegex`, for ant, regular-expression and case-insensitive regular-expression repsectively. A separate instance is created for each<<nsa-intercept-url,intercept-url>> element using its <<nsa-intercept-url-pattern,pattern>> and <<nsa-intercept-url-method,method>> attributes. Ant paths are matched using an `AntPathRequestMatcher` and regular expressions are matched using a `RegexRequestMatcher`. See the Javadoc for these classes for more details on exactly how the matching is preformed. Ant paths are the default strategy.
|
||||
Defines the `RequestMatcher` strategy used in the `FilterChainProxy` and the beans created by the `intercept-url` to match incoming requests. Options are currently `mvc`, `ant`, `regex` and `ciRegex`, for Spring MVC, ant, regular-expression and case-insensitive regular-expression respectively. A separate instance is created for each<<nsa-intercept-url,intercept-url>> element using its <<nsa-intercept-url-pattern,pattern>> and <<nsa-intercept-url-method,method>> attributes. Ant paths are matched using an `AntPathRequestMatcher` and regular expressions are matched using a `RegexRequestMatcher`. See the Javadoc for these classes for more details on exactly how the matching is performed. Ant paths are the default strategy.
|
||||
|
||||
|
||||
[[nsa-http-request-matcher-ref]]
|
||||
|
|
|
@ -33,6 +33,7 @@ import org.springframework.security.web.FilterInvocation;
|
|||
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestVariablesExtractor;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
|
@ -96,6 +97,10 @@ public final class ExpressionBasedFilterInvocationSecurityMetadataSource
|
|||
return new AntPathMatcherEvaluationContextPostProcessor(
|
||||
(AntPathRequestMatcher) request);
|
||||
}
|
||||
if (request instanceof RequestVariablesExtractor) {
|
||||
return new RequestVariablesExtractorEvaluationContextPostProcessor(
|
||||
(RequestVariablesExtractor) request);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -119,4 +124,24 @@ public final class ExpressionBasedFilterInvocationSecurityMetadataSource
|
|||
}
|
||||
}
|
||||
|
||||
static class RequestVariablesExtractorEvaluationContextPostProcessor
|
||||
extends AbstractVariableEvaluationContextPostProcessor {
|
||||
private final RequestVariablesExtractor matcher;
|
||||
|
||||
public RequestVariablesExtractorEvaluationContextPostProcessor(
|
||||
RequestVariablesExtractor matcher) {
|
||||
this.matcher = matcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
Map<String, String> extractVariables(HttpServletRequest request) {
|
||||
return this.matcher.extractUriTemplateVariables(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
String postProcessVariableName(String variableName) {
|
||||
return variableName;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* Copyright 2012-2016 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
|
||||
*
|
||||
* http://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.web.servlet.util.matcher;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestVariablesExtractor;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.PathMatcher;
|
||||
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
|
||||
import org.springframework.web.servlet.handler.MatchableHandlerMapping;
|
||||
import org.springframework.web.servlet.handler.RequestMatchResult;
|
||||
import org.springframework.web.util.UrlPathHelper;
|
||||
|
||||
/**
|
||||
* A {@link RequestMatcher} that uses Spring MVC's {@link HandlerMappingIntrospector} to
|
||||
* match the path and extract variables.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 4.1.1
|
||||
*/
|
||||
public final class MvcRequestMatcher
|
||||
implements RequestMatcher, RequestVariablesExtractor {
|
||||
private final DefaultMatcher defaultMatcher = new DefaultMatcher();
|
||||
|
||||
private final HandlerMappingIntrospector introspector;
|
||||
private final String pattern;
|
||||
private HttpMethod method;
|
||||
|
||||
public MvcRequestMatcher(HandlerMappingIntrospector introspector, String pattern) {
|
||||
this.introspector = introspector;
|
||||
this.pattern = pattern;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(HttpServletRequest request) {
|
||||
if (this.method != null && !this.method.name().equals(request.getMethod())) {
|
||||
return false;
|
||||
}
|
||||
MatchableHandlerMapping mapping = getMapping(request);
|
||||
if (mapping == null) {
|
||||
return this.defaultMatcher.matches(request);
|
||||
}
|
||||
RequestMatchResult matchResult = mapping.match(request, this.pattern);
|
||||
return matchResult != null;
|
||||
}
|
||||
|
||||
private MatchableHandlerMapping getMapping(HttpServletRequest request) {
|
||||
try {
|
||||
return this.introspector.getMatchableHandlerMapping(request);
|
||||
}
|
||||
catch (Throwable t) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.security.web.util.matcher.RequestVariablesExtractor#
|
||||
* extractUriTemplateVariables(javax.servlet.http.HttpServletRequest)
|
||||
*/
|
||||
@Override
|
||||
public Map<String, String> extractUriTemplateVariables(HttpServletRequest request) {
|
||||
MatchableHandlerMapping mapping = getMapping(request);
|
||||
if (mapping == null) {
|
||||
return this.defaultMatcher.extractUriTemplateVariables(request);
|
||||
}
|
||||
RequestMatchResult result = mapping.match(request, this.pattern);
|
||||
return result == null ? Collections.<String, String>emptyMap()
|
||||
: result.extractUriTemplateVariables();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param method the method to set
|
||||
*/
|
||||
public void setMethod(HttpMethod method) {
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
private class DefaultMatcher implements RequestMatcher, RequestVariablesExtractor {
|
||||
|
||||
private final UrlPathHelper pathHelper = new UrlPathHelper();
|
||||
|
||||
private final PathMatcher pathMatcher = new AntPathMatcher();
|
||||
|
||||
@Override
|
||||
public boolean matches(HttpServletRequest request) {
|
||||
String lookupPath = this.pathHelper.getLookupPathForRequest(request);
|
||||
return matches(lookupPath);
|
||||
}
|
||||
|
||||
private boolean matches(String lookupPath) {
|
||||
return this.pathMatcher.match(MvcRequestMatcher.this.pattern, lookupPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> extractUriTemplateVariables(
|
||||
HttpServletRequest request) {
|
||||
String lookupPath = this.pathHelper.getLookupPathForRequest(request);
|
||||
if (matches(lookupPath)) {
|
||||
return this.pathMatcher.extractUriTemplateVariables(
|
||||
MvcRequestMatcher.this.pattern, lookupPath);
|
||||
}
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright 2012-2016 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
|
||||
*
|
||||
* http://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.web.util.matcher;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* An interface for extracting URI variables from the {@link HttpServletRequest}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 4.1.1
|
||||
*/
|
||||
public interface RequestVariablesExtractor {
|
||||
|
||||
/**
|
||||
* Extract URL template variables from the request.
|
||||
*
|
||||
* @param request the HttpServletRequest to obtain a URL to extract the variables from
|
||||
* @return the URL variables or empty if no variables are found
|
||||
*/
|
||||
Map<String, String> extractUriTemplateVariables(HttpServletRequest request);
|
||||
}
|
|
@ -0,0 +1,202 @@
|
|||
/*
|
||||
* Copyright 2012-2016 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
|
||||
*
|
||||
* http://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.web.servlet.util.matcher;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
|
||||
import org.springframework.web.servlet.handler.MatchableHandlerMapping;
|
||||
import org.springframework.web.servlet.handler.RequestMatchResult;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class MvcRequestMatcherTests {
|
||||
@Mock
|
||||
HandlerMappingIntrospector introspector;
|
||||
@Mock
|
||||
MatchableHandlerMapping mapping;
|
||||
@Mock
|
||||
RequestMatchResult result;
|
||||
@Captor
|
||||
ArgumentCaptor<String> pattern;
|
||||
MockHttpServletRequest request;
|
||||
|
||||
MvcRequestMatcher matcher;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
this.request = new MockHttpServletRequest();
|
||||
this.request.setMethod("GET");
|
||||
this.request.setServletPath("/path");
|
||||
this.matcher = new MvcRequestMatcher(this.introspector, "/path");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void extractUriTemplateVariablesSuccess() throws Exception {
|
||||
when(this.result.extractUriTemplateVariables())
|
||||
.thenReturn(Collections.singletonMap("p", "path"));
|
||||
when(this.introspector.getMatchableHandlerMapping(this.request))
|
||||
.thenReturn(this.mapping);
|
||||
when(this.mapping.match(eq(this.request), this.pattern.capture()))
|
||||
.thenReturn(this.result);
|
||||
|
||||
this.matcher = new MvcRequestMatcher(this.introspector, "/{p}");
|
||||
when(this.introspector.getMatchableHandlerMapping(this.request)).thenReturn(null);
|
||||
|
||||
assertThat(this.matcher.extractUriTemplateVariables(this.request))
|
||||
.containsEntry("p", "path");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void extractUriTemplateVariablesFail() throws Exception {
|
||||
when(this.result.extractUriTemplateVariables())
|
||||
.thenReturn(Collections.<String, String>emptyMap());
|
||||
when(this.introspector.getMatchableHandlerMapping(this.request))
|
||||
.thenReturn(this.mapping);
|
||||
when(this.mapping.match(eq(this.request), this.pattern.capture()))
|
||||
.thenReturn(this.result);
|
||||
|
||||
assertThat(this.matcher.extractUriTemplateVariables(this.request)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void extractUriTemplateVariablesDefaultSuccess() throws Exception {
|
||||
this.matcher = new MvcRequestMatcher(this.introspector, "/{p}");
|
||||
when(this.introspector.getMatchableHandlerMapping(this.request)).thenReturn(null);
|
||||
|
||||
assertThat(this.matcher.extractUriTemplateVariables(this.request))
|
||||
.containsEntry("p", "path");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void extractUriTemplateVariablesDefaultFail() throws Exception {
|
||||
this.matcher = new MvcRequestMatcher(this.introspector, "/nomatch/{p}");
|
||||
when(this.introspector.getMatchableHandlerMapping(this.request)).thenReturn(null);
|
||||
|
||||
assertThat(this.matcher.extractUriTemplateVariables(this.request)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesPathOnlyTrue() throws Exception {
|
||||
when(this.introspector.getMatchableHandlerMapping(this.request))
|
||||
.thenReturn(this.mapping);
|
||||
when(this.mapping.match(eq(this.request), this.pattern.capture()))
|
||||
.thenReturn(this.result);
|
||||
|
||||
assertThat(this.matcher.matches(this.request)).isTrue();
|
||||
assertThat(this.pattern.getValue()).isEqualTo("/path");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesDefaultMatches() throws Exception {
|
||||
when(this.introspector.getMatchableHandlerMapping(this.request)).thenReturn(null);
|
||||
|
||||
assertThat(this.matcher.matches(this.request)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesDefaultDoesNotMatch() throws Exception {
|
||||
this.request.setServletPath("/other");
|
||||
when(this.introspector.getMatchableHandlerMapping(this.request)).thenReturn(null);
|
||||
|
||||
assertThat(this.matcher.matches(this.request)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesPathOnlyFalse() throws Exception {
|
||||
when(this.introspector.getMatchableHandlerMapping(this.request))
|
||||
.thenReturn(this.mapping);
|
||||
|
||||
assertThat(this.matcher.matches(this.request)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesMethodAndPathTrue() throws Exception {
|
||||
this.matcher.setMethod(HttpMethod.GET);
|
||||
when(this.introspector.getMatchableHandlerMapping(this.request))
|
||||
.thenReturn(this.mapping);
|
||||
when(this.mapping.match(eq(this.request), this.pattern.capture()))
|
||||
.thenReturn(this.result);
|
||||
|
||||
assertThat(this.matcher.matches(this.request)).isTrue();
|
||||
assertThat(this.pattern.getValue()).isEqualTo("/path");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesMethodAndPathFalseMethod() throws Exception {
|
||||
this.matcher.setMethod(HttpMethod.POST);
|
||||
|
||||
assertThat(this.matcher.matches(this.request)).isFalse();
|
||||
// method compare should be done first since faster
|
||||
verifyZeroInteractions(this.introspector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Malicious users can specify any HTTP Method to create a stacktrace and try to
|
||||
* expose useful information about the system. We should ensure we ignore invalid HTTP
|
||||
* methods.
|
||||
* @throws Exception if an error occurs
|
||||
*/
|
||||
@Test
|
||||
public void matchesInvalidMethodOnRequest() throws Exception {
|
||||
this.matcher.setMethod(HttpMethod.GET);
|
||||
this.request.setMethod("invalid");
|
||||
|
||||
assertThat(this.matcher.matches(this.request)).isFalse();
|
||||
// method compare should be done first since faster
|
||||
verifyZeroInteractions(this.introspector);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesMethodAndPathFalsePath() throws Exception {
|
||||
this.matcher.setMethod(HttpMethod.GET);
|
||||
when(this.introspector.getMatchableHandlerMapping(this.request))
|
||||
.thenReturn(this.mapping);
|
||||
|
||||
assertThat(this.matcher.matches(this.request)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesGetMatchableHandlerMappingNull() throws Exception {
|
||||
assertThat(this.matcher.matches(this.request)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesGetMatchableHandlerMappingThrows() throws Exception {
|
||||
when(this.introspector.getMatchableHandlerMapping(this.request)).thenThrow(
|
||||
new HttpRequestMethodNotSupportedException(this.request.getMethod()));
|
||||
assertThat(this.matcher.matches(this.request)).isTrue();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue