1
0
mirror of synced 2026-08-05 17:57:15 +00:00

MvcRequestMatcher servletPath / JavaConfig

Issue: gh-3987
This commit is contained in:
Rob Winch
2016-07-21 00:40:43 -05:00
parent 050198e51b
commit 3befb1c8a6
22 changed files with 1302 additions and 62 deletions
@@ -17,16 +17,24 @@ package org.springframework.security.config.annotation.web;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import javax.servlet.ServletContext;
import javax.servlet.ServletRegistration;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.ObjectPostProcessor;
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.util.ClassUtils;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
/**
@@ -48,6 +56,15 @@ public abstract class AbstractRequestMatcherRegistry<C> {
this.context = context;
}
/**
* Gets the {@link ApplicationContext}
*
* @return the {@link ApplicationContext}
*/
protected final ApplicationContext getApplicationContext() {
return this.context;
}
/**
* Maps any request.
*
@@ -117,9 +134,7 @@ public abstract class AbstractRequestMatcherRegistry<C> {
* Spring MVC
* @return the object that is chained after creating the {@link RequestMatcher}.
*/
public C mvcMatchers(String... mvcPatterns) {
return mvcMatchers(null, mvcPatterns);
}
public abstract C mvcMatchers(String... mvcPatterns);
/**
* <p>
@@ -138,18 +153,37 @@ public abstract class AbstractRequestMatcherRegistry<C> {
* Spring MVC
* @return the object that is chained after creating the {@link RequestMatcher}.
*/
public C mvcMatchers(HttpMethod method, String... mvcPatterns) {
public abstract C mvcMatchers(HttpMethod method, String... mvcPatterns);
/**
* Creates {@link MvcRequestMatcher} instances for the method and patterns passed in
*
* @param method the HTTP method to use or null if any should be used
* @param mvcPatterns the Spring MVC patterns to match on
* @return a List of {@link MvcRequestMatcher} instances
*/
protected final List<MvcRequestMatcher> createMvcMatchers(HttpMethod method,
String... mvcPatterns) {
boolean isServlet30 = ClassUtils.isPresent("javax.servlet.ServletRegistration", getClass().getClassLoader());
ObjectPostProcessor<Object> opp = this.context.getBean(ObjectPostProcessor.class);
HandlerMappingIntrospector introspector = new HandlerMappingIntrospector(
this.context);
List<RequestMatcher> matchers = new ArrayList<RequestMatcher>(mvcPatterns.length);
List<MvcRequestMatcher> matchers = new ArrayList<MvcRequestMatcher>(
mvcPatterns.length);
for (String mvcPattern : mvcPatterns) {
MvcRequestMatcher matcher = new MvcRequestMatcher(introspector, mvcPattern);
MvcRequestMatcher matcher;
if(isServlet30) {
matcher = new ServletPathValidatingtMvcRequestMatcher(introspector, mvcPattern);
opp.postProcess(matcher);
} else {
matcher = new MvcRequestMatcher(introspector, mvcPattern);
}
if (method != null) {
matcher.setMethod(method);
}
matchers.add(matcher);
}
return chainRequestMatchers(matchers);
return matchers;
}
/**
@@ -281,4 +315,49 @@ public abstract class AbstractRequestMatcherRegistry<C> {
private RequestMatchers() {
}
}
static class ServletPathValidatingtMvcRequestMatcher extends MvcRequestMatcher implements SmartInitializingSingleton, ServletContextAware {
private ServletContext servletContext;
/**
* @param introspector
* @param pattern
*/
public ServletPathValidatingtMvcRequestMatcher(HandlerMappingIntrospector introspector,
String pattern) {
super(introspector, pattern);
}
/* (non-Javadoc)
* @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
*/
@Override
public void afterSingletonsInstantiated() {
if(getServletPath() != null) {
return;
}
Collection<? extends ServletRegistration> registrations = servletContext.getServletRegistrations().values();
for(ServletRegistration registration : registrations) {
Collection<String> mappings = registration.getMappings();
for(String mapping : mappings) {
if(mapping.startsWith("/") && mapping.length() > 1) {
throw new IllegalStateException(
"servletPath must not be null for mvcPattern \"" + getMvcPattern()
+ "\" when providing a servlet mapping of "
+ mapping + " for servlet "
+ registration.getClassName());
}
}
}
}
/* (non-Javadoc)
* @see org.springframework.web.context.ServletContextAware#setServletContext(javax.servlet.ServletContext)
*/
@Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
}
}
@@ -0,0 +1,94 @@
/*
* 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.annotation.web;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import javax.servlet.Registration;
import javax.servlet.ServletContext;
import javax.servlet.ServletRegistration;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import org.springframework.mock.web.MockServletContext;
import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry.ServletPathValidatingtMvcRequestMatcher;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
/**
* @author Rob Winch
*/
@RunWith(MockitoJUnitRunner.class)
public class AbstractRequestMatcherRegistryTests {
@Mock
HandlerMappingIntrospector introspector;
ServletPathValidatingtMvcRequestMatcher matcher;
ServletContext servletContext;
@Before
public void setup() {
servletContext = spy(new MockServletContext());
matcher = new ServletPathValidatingtMvcRequestMatcher(introspector, "/foo");
matcher.setServletContext(servletContext);
}
@Test(expected = IllegalStateException.class)
public void servletPathValidatingtMvcRequestMatcherAfterSingletonsIntantiatedFailsWithSpringServlet() {
setMappings("/spring");
matcher.afterSingletonsInstantiated();
}
@Test
public void servletPathValidatingtMvcRequestMatcherAfterSingletonsIntantiatedWithSpringServlet() {
matcher.setServletPath("/spring");
setMappings("/spring");
matcher.afterSingletonsInstantiated();
}
@Test
public void servletPathValidatingtMvcRequestMatcherAfterSingletonsIntantiatedDefaultServlet() {
setMappings("/");
matcher.afterSingletonsInstantiated();
}
private void setMappings(String... mappings) {
final ServletRegistration registration = mock(ServletRegistration.class);
when(registration.getMappings()).thenReturn(Arrays.asList(mappings));
Answer<Map<String, ? extends ServletRegistration>> answer = new Answer<Map<String, ? extends ServletRegistration>>() {
@Override
public Map<String, ? extends ServletRegistration> answer(InvocationOnMock invocation) throws Throwable {
return Collections.<String, ServletRegistration>singletonMap("spring", registration);
}
};
when(servletContext.getServletRegistrations()).thenAnswer(answer);
}
}
@@ -24,6 +24,7 @@ import javax.servlet.Filter;
import javax.servlet.http.HttpServletRequest;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder;
@@ -1269,16 +1270,45 @@ public final class HttpSecurity extends
return requestMatcher(new RegexRequestMatcher(pattern, null));
}
/**
* An extension to {@link RequestMatcherConfigurer} that allows optionally configuring
* the servlet path.
*
* @author Rob Winch
*/
public final class MvcMatchersRequestMatcherConfigurer extends RequestMatcherConfigurer {
/**
* Creates a new instance
* @param context the {@link ApplicationContext} to use
* @param matchers the {@link MvcRequestMatcher} instances to set the servlet path
* on if {@link #servletPath(String)} is set.
*/
private MvcMatchersRequestMatcherConfigurer(ApplicationContext context,
List<MvcRequestMatcher> matchers) {
super(context);
this.matchers = new ArrayList<RequestMatcher>(matchers);
}
public RequestMatcherConfigurer servletPath(String servletPath) {
for (RequestMatcher matcher : this.matchers) {
((MvcRequestMatcher) matcher).setServletPath(servletPath);
}
return this;
}
}
/**
* Allows mapping HTTP requests that this {@link HttpSecurity} will be used for
*
* @author Rob Winch
* @since 3.2
*/
public final class RequestMatcherConfigurer extends
AbstractRequestMatcherRegistry<RequestMatcherConfigurer> {
public class RequestMatcherConfigurer
extends AbstractRequestMatcherRegistry<RequestMatcherConfigurer> {
private List<RequestMatcher> matchers = new ArrayList<RequestMatcher>();
protected List<RequestMatcher> matchers = new ArrayList<RequestMatcher>();
/**
* @param context
@@ -1287,13 +1317,31 @@ public final class HttpSecurity extends
setApplicationContext(context);
}
@Override
public MvcMatchersRequestMatcherConfigurer mvcMatchers(HttpMethod method,
String... mvcPatterns) {
List<MvcRequestMatcher> mvcMatchers = createMvcMatchers(method, mvcPatterns);
setMatchers(mvcMatchers);
return new MvcMatchersRequestMatcherConfigurer(getContext(), mvcMatchers);
}
@Override
public MvcMatchersRequestMatcherConfigurer mvcMatchers(String... patterns) {
return mvcMatchers(null, patterns);
}
@Override
protected RequestMatcherConfigurer chainRequestMatchers(
List<RequestMatcher> requestMatchers) {
matchers.addAll(requestMatchers);
requestMatcher(new OrRequestMatcher(matchers));
setMatchers(requestMatchers);
return this;
}
private void setMatchers(List<? extends RequestMatcher> requestMatchers) {
this.matchers.addAll(requestMatchers);
requestMatcher(new OrRequestMatcher(this.matchers));
}
/**
* Return the {@link HttpSecurity} for further customizations
*
@@ -27,6 +27,7 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder;
import org.springframework.security.config.annotation.ObjectPostProcessor;
@@ -48,6 +49,7 @@ import org.springframework.security.web.access.intercept.FilterSecurityIntercept
import org.springframework.security.web.debug.DebugFilter;
import org.springframework.security.web.firewall.DefaultHttpFirewall;
import org.springframework.security.web.firewall.HttpFirewall;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.web.filter.DelegatingFilterProxy;
@@ -307,6 +309,30 @@ public final class WebSecurity extends
return result;
}
/**
* An {@link IgnoredRequestConfigurer} that allows optionally configuring the
* {@link MvcRequestMatcher#setMethod(HttpMethod)}
*
* @author Rob Winch
*/
public final class MvcMatchersIgnoredRequestConfigurer
extends IgnoredRequestConfigurer {
private final List<MvcRequestMatcher> mvcMatchers;
private MvcMatchersIgnoredRequestConfigurer(ApplicationContext context,
List<MvcRequestMatcher> mvcMatchers) {
super(context);
this.mvcMatchers = mvcMatchers;
}
public IgnoredRequestConfigurer servletPath(String servletPath) {
for (MvcRequestMatcher matcher : this.mvcMatchers) {
matcher.setServletPath(servletPath);
}
return this;
}
}
/**
* Allows registering {@link RequestMatcher} instances that should be ignored by
* Spring Security.
@@ -314,17 +340,31 @@ public final class WebSecurity extends
* @author Rob Winch
* @since 3.2
*/
public final class IgnoredRequestConfigurer extends
AbstractRequestMatcherRegistry<IgnoredRequestConfigurer> {
public class IgnoredRequestConfigurer
extends AbstractRequestMatcherRegistry<IgnoredRequestConfigurer> {
private IgnoredRequestConfigurer(ApplicationContext context) {
setApplicationContext(context);
}
@Override
public MvcMatchersIgnoredRequestConfigurer mvcMatchers(HttpMethod method,
String... mvcPatterns) {
List<MvcRequestMatcher> mvcMatchers = createMvcMatchers(method, mvcPatterns);
WebSecurity.this.ignoredRequests.addAll(mvcMatchers);
return new MvcMatchersIgnoredRequestConfigurer(getApplicationContext(),
mvcMatchers);
}
@Override
public MvcMatchersIgnoredRequestConfigurer mvcMatchers(String... mvcPatterns) {
return mvcMatchers(null, mvcPatterns);
}
@Override
protected IgnoredRequestConfigurer chainRequestMatchers(
List<RequestMatcher> requestMatchers) {
ignoredRequests.addAll(requestMatchers);
WebSecurity.this.ignoredRequests.addAll(requestMatchers);
return this;
}
@@ -21,6 +21,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.config.annotation.ObjectPostProcessor;
@@ -37,6 +38,7 @@ import org.springframework.security.web.access.channel.RetryWithHttpEntryPoint;
import org.springframework.security.web.access.channel.RetryWithHttpsEntryPoint;
import org.springframework.security.web.access.channel.SecureChannelProcessor;
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
/**
@@ -136,7 +138,7 @@ public final class ChannelSecurityConfigurer<H extends HttpSecurityBuilder<H>> e
}
private ChannelRequestMatcherRegistry addAttribute(String attribute,
List<RequestMatcher> matchers) {
List<? extends RequestMatcher> matchers) {
for (RequestMatcher matcher : matchers) {
Collection<ConfigAttribute> attrs = Arrays
.<ConfigAttribute> asList(new SecurityConfig(attribute));
@@ -145,13 +147,25 @@ public final class ChannelSecurityConfigurer<H extends HttpSecurityBuilder<H>> e
return REGISTRY;
}
public final class ChannelRequestMatcherRegistry extends
AbstractConfigAttributeRequestMatcherRegistry<RequiresChannelUrl> {
public final class ChannelRequestMatcherRegistry
extends AbstractConfigAttributeRequestMatcherRegistry<RequiresChannelUrl> {
private ChannelRequestMatcherRegistry(ApplicationContext context) {
setApplicationContext(context);
}
@Override
public MvcMatchersRequiresChannelUrl mvcMatchers(HttpMethod method,
String... mvcPatterns) {
List<MvcRequestMatcher> mvcMatchers = createMvcMatchers(method, mvcPatterns);
return new MvcMatchersRequiresChannelUrl(mvcMatchers);
}
@Override
public MvcMatchersRequiresChannelUrl mvcMatchers(String... patterns) {
return mvcMatchers(null, patterns);
}
@Override
protected RequiresChannelUrl chainRequestMatchersInternal(
List<RequestMatcher> requestMatchers) {
@@ -193,10 +207,24 @@ public final class ChannelSecurityConfigurer<H extends HttpSecurityBuilder<H>> e
}
}
public final class RequiresChannelUrl {
private List<RequestMatcher> requestMatchers;
public final class MvcMatchersRequiresChannelUrl extends RequiresChannelUrl {
private RequiresChannelUrl(List<RequestMatcher> requestMatchers) {
private MvcMatchersRequiresChannelUrl(List<MvcRequestMatcher> matchers) {
super(matchers);
}
public RequiresChannelUrl servletPath(String servletPath) {
for (RequestMatcher matcher : this.requestMatchers) {
((MvcRequestMatcher) matcher).setServletPath(servletPath);
}
return this;
}
}
public class RequiresChannelUrl {
protected List<? extends RequestMatcher> requestMatchers;
private RequiresChannelUrl(List<? extends RequestMatcher> requestMatchers) {
this.requestMatchers = requestMatchers;
}
@@ -22,6 +22,7 @@ import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
@@ -36,6 +37,7 @@ import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
import org.springframework.security.web.csrf.LazyCsrfTokenRepository;
import org.springframework.security.web.csrf.MissingCsrfTokenException;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.security.web.session.InvalidSessionAccessDeniedHandler;
import org.springframework.security.web.session.InvalidSessionStrategy;
import org.springframework.security.web.util.matcher.AndRequestMatcher;
@@ -276,6 +278,20 @@ public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
setApplicationContext(context);
}
@Override
public MvcMatchersIgnoreCsrfProtectionRegistry mvcMatchers(HttpMethod method,
String... mvcPatterns) {
List<MvcRequestMatcher> mvcMatchers = createMvcMatchers(method, mvcPatterns);
CsrfConfigurer.this.ignoredCsrfProtectionMatchers.addAll(mvcMatchers);
return new MvcMatchersIgnoreCsrfProtectionRegistry(getApplicationContext(),
mvcMatchers);
}
@Override
public MvcMatchersIgnoreCsrfProtectionRegistry mvcMatchers(String... mvcPatterns) {
return mvcMatchers(null, mvcPatterns);
}
public CsrfConfigurer<H> and() {
return CsrfConfigurer.this;
}
@@ -287,4 +303,28 @@ public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
return this;
}
}
/**
* An {@link IgnoreCsrfProtectionRegistry} that allows optionally configuring the
* {@link MvcRequestMatcher#setMethod(HttpMethod)}
*
* @author Rob Winch
*/
private final class MvcMatchersIgnoreCsrfProtectionRegistry
extends IgnoreCsrfProtectionRegistry {
private final List<MvcRequestMatcher> mvcMatchers;
private MvcMatchersIgnoreCsrfProtectionRegistry(ApplicationContext context,
List<MvcRequestMatcher> mvcMatchers) {
super(context);
this.mvcMatchers = mvcMatchers;
}
public IgnoreCsrfProtectionRegistry servletPath(String servletPath) {
for (MvcRequestMatcher matcher : this.mvcMatchers) {
matcher.setServletPath(servletPath);
}
return this;
}
}
}
@@ -21,6 +21,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
@@ -34,6 +35,7 @@ import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.security.web.access.expression.ExpressionBasedFilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.expression.WebExpressionVoter;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -111,6 +113,16 @@ public final class ExpressionUrlAuthorizationConfigurer<H extends HttpSecurityBu
setApplicationContext(context);
}
@Override
public MvcMatchersAuthorizedUrl mvcMatchers(HttpMethod method, String... mvcPatterns) {
return new MvcMatchersAuthorizedUrl(createMvcMatchers(method, mvcPatterns));
}
@Override
public MvcMatchersAuthorizedUrl mvcMatchers(String... patterns) {
return mvcMatchers(null, patterns);
}
@Override
protected final AuthorizedUrl chainRequestMatchersInternal(
List<RequestMatcher> requestMatchers) {
@@ -241,8 +253,32 @@ public final class ExpressionUrlAuthorizationConfigurer<H extends HttpSecurityBu
return "hasIpAddress('" + ipAddressExpression + "')";
}
public final class AuthorizedUrl {
private List<RequestMatcher> requestMatchers;
/**
* An {@link AuthorizedUrl} that allows optionally configuring the
* {@link MvcRequestMatcher#setMethod(HttpMethod)}
*
* @author Rob Winch
*/
public class MvcMatchersAuthorizedUrl extends AuthorizedUrl {
/**
* Creates a new instance
*
* @param requestMatchers the {@link RequestMatcher} instances to map
*/
private MvcMatchersAuthorizedUrl(List<MvcRequestMatcher> requestMatchers) {
super(requestMatchers);
}
public AuthorizedUrl servletPath(String servletPath) {
for (MvcRequestMatcher matcher : (List<MvcRequestMatcher>) getMatchers()) {
matcher.setServletPath(servletPath);
}
return this;
}
}
public class AuthorizedUrl {
private List<? extends RequestMatcher> requestMatchers;
private boolean not;
/**
@@ -250,10 +286,14 @@ public final class ExpressionUrlAuthorizationConfigurer<H extends HttpSecurityBu
*
* @param requestMatchers the {@link RequestMatcher} instances to map
*/
private AuthorizedUrl(List<RequestMatcher> requestMatchers) {
private AuthorizedUrl(List<? extends RequestMatcher> requestMatchers) {
this.requestMatchers = requestMatchers;
}
protected List<? extends RequestMatcher> getMatchers() {
return this.requestMatchers;
}
/**
* Negates the following expression.
*
@@ -20,6 +20,7 @@ import java.util.Collection;
import java.util.List;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.ConfigAttribute;
@@ -28,8 +29,10 @@ import org.springframework.security.access.vote.AuthenticatedVoter;
import org.springframework.security.access.vote.RoleVoter;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer.AuthorizedUrl;
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
@@ -126,6 +129,17 @@ public final class UrlAuthorizationConfigurer<H extends HttpSecurityBuilder<H>>
setApplicationContext(context);
}
@Override
public MvcMatchersAuthorizedUrl mvcMatchers(HttpMethod method,
String... mvcPatterns) {
return new MvcMatchersAuthorizedUrl(createMvcMatchers(method, mvcPatterns));
}
@Override
public MvcMatchersAuthorizedUrl mvcMatchers(String... patterns) {
return mvcMatchers(null, patterns);
}
@Override
protected final AuthorizedUrl chainRequestMatchersInternal(
List<RequestMatcher> requestMatchers) {
@@ -237,6 +251,31 @@ public final class UrlAuthorizationConfigurer<H extends HttpSecurityBuilder<H>>
return authorities;
}
/**
* An {@link AuthorizedUrl} that allows optionally configuring the
* {@link MvcRequestMatcher#setMethod(HttpMethod)}
*
* @author Rob Winch
*/
public final class MvcMatchersAuthorizedUrl extends AuthorizedUrl {
/**
* Creates a new instance
*
* @param requestMatchers the {@link RequestMatcher} instances to map
*/
private MvcMatchersAuthorizedUrl(List<MvcRequestMatcher> requestMatchers) {
super(requestMatchers);
}
@SuppressWarnings("unchecked")
public AuthorizedUrl servletPath(String servletPath) {
for (MvcRequestMatcher matcher : (List<MvcRequestMatcher>) getMatchers()) {
matcher.setServletPath(servletPath);
}
return this;
}
}
/**
* Maps the specified {@link RequestMatcher} instances to {@link ConfigAttribute}
* instances.
@@ -244,15 +283,15 @@ public final class UrlAuthorizationConfigurer<H extends HttpSecurityBuilder<H>>
* @author Rob Winch
* @since 3.2
*/
public final class AuthorizedUrl {
private final List<RequestMatcher> requestMatchers;
public class AuthorizedUrl {
private final List<? extends RequestMatcher> requestMatchers;
/**
* Creates a new instance
* @param requestMatchers the {@link RequestMatcher} instances to map to some
* {@link ConfigAttribute} instances.
*/
private AuthorizedUrl(List<RequestMatcher> requestMatchers) {
private AuthorizedUrl(List<? extends RequestMatcher> requestMatchers) {
Assert.notEmpty(requestMatchers,
"requestMatchers must contain at least one value");
this.requestMatchers = requestMatchers;
@@ -318,5 +357,9 @@ public final class UrlAuthorizationConfigurer<H extends HttpSecurityBuilder<H>>
addMapping(requestMatchers, SecurityConfig.createList(attributes));
return UrlAuthorizationConfigurer.this.REGISTRY;
}
protected List<? extends RequestMatcher> getMatchers() {
return this.requestMatchers;
}
}
}
@@ -15,8 +15,6 @@
*/
package org.springframework.security.config.http;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.config.BeanDefinition;
@@ -37,6 +35,8 @@ import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
import java.util.List;
/**
* Allows for convenient creation of a {@link FilterInvocationSecurityMetadataSource} bean
* for use with a FilterSecurityInterceptor.
@@ -48,12 +48,13 @@ public class FilterInvocationSecurityMetadataSourceParser implements BeanDefinit
private static final String ATT_HTTP_METHOD = "method";
private static final String ATT_PATTERN = "pattern";
private static final String ATT_ACCESS = "access";
private static final String ATT_SERVLET_PATH = "servlet-path";
private static final Log logger = LogFactory
.getLog(FilterInvocationSecurityMetadataSourceParser.class);
public BeanDefinition parse(Element element, ParserContext parserContext) {
List<Element> interceptUrls = DomUtils.getChildElementsByTagName(element,
"intercept-url");
Elements.INTERCEPT_URL);
// Check for attributes that aren't allowed in this context
for (Element elt : interceptUrls) {
@@ -71,6 +72,12 @@ public class FilterInvocationSecurityMetadataSourceParser implements BeanDefinit
"The attribute '" + HttpSecurityBeanDefinitionParser.ATT_FILTERS
+ "' isn't allowed here.", elt);
}
if (StringUtils.hasLength(elt.getAttribute(ATT_SERVLET_PATH))) {
parserContext.getReaderContext().error(
"The attribute '" + ATT_SERVLET_PATH
+ "' isn't allowed here.", elt);
}
}
BeanDefinition mds = createSecurityMetadataSource(interceptUrls, false, element,
@@ -165,8 +172,16 @@ public class FilterInvocationSecurityMetadataSourceParser implements BeanDefinit
method = null;
}
String servletPath = urlElt.getAttribute(ATT_SERVLET_PATH);
if (!StringUtils.hasText(servletPath)) {
servletPath = null;
} else if (!MatcherType.mvc.equals(matcherType)) {
parserContext.getReaderContext().error(
ATT_SERVLET_PATH + " is not applicable for request-matcher: '" + matcherType.name() + "'", urlElt);
}
BeanDefinition matcher = matcherType.createMatcher(parserContext, path,
method);
method, servletPath);
BeanDefinitionBuilder attributeBuilder = BeanDefinitionBuilder
.rootBeanDefinition(SecurityConfig.class);
@@ -49,6 +49,10 @@ public enum MatcherType {
}
public BeanDefinition createMatcher(ParserContext pc, String path, String method) {
return createMatcher(pc, path, method, null);
}
public BeanDefinition createMatcher(ParserContext pc, String path, String method, String servletPath) {
if (("/**".equals(path) || "**".equals(path)) && method == null) {
return new RootBeanDefinition(AnyRequestMatcher.class);
}
@@ -74,6 +78,7 @@ public enum MatcherType {
matcherBldr.addConstructorArgValue(path);
if (this == mvc) {
matcherBldr.addPropertyValue("method", method);
matcherBldr.addPropertyValue("servletPath", servletPath);
}
else {
matcherBldr.addConstructorArgValue(method);
@@ -381,6 +381,9 @@ intercept-url.attlist &=
intercept-url.attlist &=
## Used to specify that a URL must be accessed over http or https, or that there is no preference. The value should be "http", "https" or "any", respectively.
attribute requires-channel {xsd:token}?
intercept-url.attlist &=
## The path to the servlet. This attribute is only applicable when 'request-matcher' is 'mvc'. In addition, the value is only required in the following 2 use cases: 1) There are 2 or more HttpServlet's registered in the ServletContext that have mappings starting with '/' and are different; 2) The pattern starts with the same value of a registered HttpServlet path, excluding the default (root) HttpServlet '/'.
attribute servlet-path {xsd:token}?
logout =
## Incorporates a logout processing filter. Most web applications require a logout filter, although you may not require one if you write a controller to provider similar logic.
@@ -1345,6 +1345,16 @@
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="servlet-path" type="xs:token">
<xs:annotation>
<xs:documentation>The path to the servlet. This attribute is only applicable when 'request-matcher' is
'mvc'. In addition, the value is only required in the following 2 use cases: 1) There are
2 or more HttpServlet's registered in the ServletContext that have mappings starting with
'/' and are different; 2) The pattern starts with the same value of a registered
HttpServlet path, excluding the default (root) HttpServlet '/'
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:attributeGroup name="logout.attlist">
@@ -67,5 +67,13 @@ class AbstractConfigAttributeRequestMatcherRegistryTests extends Specification {
List<RequestMatcher> chainRequestMatchersInternal(List<RequestMatcher> requestMatchers) {
return requestMatchers;
}
List<RequestMatcher> mvcMatchers(String... mvcPatterns) {
null
}
List<RequestMatcher> mvcMatchers(HttpMethod method, String... mvcPatterns) {
null
}
}
}
@@ -15,19 +15,17 @@
*/
package org.springframework.security.config.http
import javax.servlet.Filter
import javax.servlet.http.HttpServletResponse
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException
import org.springframework.mock.web.MockFilterChain
import org.springframework.mock.web.MockHttpServletRequest
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;
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import javax.servlet.http.HttpServletResponse
/**
*
* @author Rob Winch
@@ -266,6 +264,88 @@ class InterceptUrlConfigTests extends AbstractHttpConfigTests {
response.status == HttpServletResponse.SC_FORBIDDEN
}
def "intercept-url mvc matchers with servlet path"() {
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", 'servlet-path': "/spring")
}
bean('pathController',PathController)
xml.'mvc:annotation-driven'()
createAppContext()
when:
request.servletPath = "/spring"
request.requestURI = "/spring/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 = "/spring"
request.requestURI = "/spring/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 = "/spring"
request.requestURI = "/spring/path/"
springSecurityFilterChain.doFilter(request, response, chain)
then:
response.status == HttpServletResponse.SC_UNAUTHORIZED
}
def "intercept-url ant matcher with servlet path fails"() {
when:
xml.http('request-matcher':'ant') {
'http-basic'()
'intercept-url'(pattern: '/path', access: "denyAll", 'servlet-path': "/spring")
}
createAppContext()
then:
thrown(BeanDefinitionParsingException)
}
def "intercept-url regex matcher with servlet path fails"() {
when:
xml.http('request-matcher':'regex') {
'http-basic'()
'intercept-url'(pattern: '/path', access: "denyAll", 'servlet-path': "/spring")
}
createAppContext()
then:
thrown(BeanDefinitionParsingException)
}
def "intercept-url ciRegex matcher with servlet path fails"() {
when:
xml.http('request-matcher':'ciRegex') {
'http-basic'()
'intercept-url'(pattern: '/path', access: "denyAll", 'servlet-path': "/spring")
}
createAppContext()
then:
thrown(BeanDefinitionParsingException)
}
def "intercept-url default matcher with servlet path fails"() {
when:
xml.http() {
'http-basic'()
'intercept-url'(pattern: '/path', access: "denyAll", 'servlet-path': "/spring")
}
createAppContext()
then:
thrown(BeanDefinitionParsingException)
}
public static class Id {
public boolean isOne(int i) {
return i == 1;
@@ -284,4 +364,4 @@ class InterceptUrlConfigTests extends AbstractHttpConfigTests {
return "path";
}
}
}
}
@@ -0,0 +1,228 @@
/*
* 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.annotation.web.builders;
import javax.servlet.http.HttpServletResponse;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
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.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.FilterChainProxy;
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;
/**
* @author Rob Winch
*/
public class WebSecurityTests {
AnnotationConfigWebApplicationContext context;
MockHttpServletRequest request;
MockHttpServletResponse response;
MockFilterChain chain;
@Autowired
FilterChainProxy springSecurityFilterChain;
@Before
public void setup() {
this.request = new MockHttpServletRequest();
this.request.setMethod("GET");
this.response = new MockHttpServletResponse();
this.chain = new MockFilterChain();
}
@After
public void cleanup() {
if (this.context != null) {
this.context.close();
}
}
@Test
public void ignoringMvcMatcher() throws Exception {
loadConfig(MvcMatcherConfig.class);
this.request.setRequestURI("/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
setup();
this.request.setRequestURI("/path.html");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
setup();
this.request.setRequestURI("/path/");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
setup();
this.request.setRequestURI("/other");
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
public void configure(WebSecurity web) throws Exception {
// @formatter:off
web
.ignoring()
.mvcMatchers("/path");
// @formatter:on
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.httpBasic().and()
.authorizeRequests()
.anyRequest().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 ignoringMvcMatcherServletPath() throws Exception {
loadConfig(MvcMatcherServletPathConfig.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_OK);
setup();
this.request.setServletPath("/spring");
this.request.setRequestURI("/spring/path.html");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
setup();
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_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_UNAUTHORIZED);
}
@EnableWebSecurity
@Configuration
@EnableWebMvc
static class MvcMatcherServletPathConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
// @formatter:off
web
.ignoring()
.mvcMatchers("/path").servletPath("/spring")
.mvcMatchers("/notused");
// @formatter:on
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.httpBasic().and()
.authorizeRequests()
.anyRequest().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";
}
}
}
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);
}
}
@@ -15,11 +15,17 @@
*/
package org.springframework.security.config.annotation.web.configurers;
import java.util.Collections;
import java.util.Map;
import javax.servlet.ServletRegistration;
import javax.servlet.http.HttpServletResponse;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
@@ -48,6 +54,9 @@ import org.springframework.web.context.support.AnnotationConfigWebApplicationCon
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
/**
* @author Rob Winch
@@ -59,12 +68,14 @@ public class AuthorizeRequestsTests {
MockHttpServletRequest request;
MockHttpServletResponse response;
MockFilterChain chain;
MockServletContext servletContext;
@Autowired
FilterChainProxy springSecurityFilterChain;
@Before
public void setup() {
this.servletContext = spy(new MockServletContext());
this.request = new MockHttpServletRequest();
this.request.setMethod("GET");
this.response = new MockHttpServletResponse();
@@ -257,7 +268,7 @@ public class AuthorizeRequestsTests {
public void mvcMatcher() throws Exception {
loadConfig(MvcMatcherConfig.class);
this.request.setServletPath("/path");
this.request.setRequestURI("/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus())
@@ -265,7 +276,7 @@ public class AuthorizeRequestsTests {
setup();
this.request.setServletPath("/path.html");
this.request.setRequestURI("/path.html");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus())
@@ -311,22 +322,100 @@ public class AuthorizeRequestsTests {
}
}
@Test
public void mvcMatcherServletPath() throws Exception {
loadConfig(MvcMatcherServletPathConfig.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("/spring");
this.request.setRequestURI("/spring/path.html");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus())
.isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
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("/foo");
this.request.setRequestURI("/foo/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
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);
}
@EnableWebSecurity
@Configuration
@EnableWebMvc
static class MvcMatcherServletPathConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.httpBasic().and()
.authorizeRequests()
.mvcMatchers("/path").servletPath("/spring").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.request.setRequestURI("/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.request.setRequestURI("/user/deny");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
assertThat(this.response.getStatus())
.isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
}
@EnableWebSecurity
@@ -360,10 +449,55 @@ public class AuthorizeRequestsTests {
}
}
@Test(expected = IllegalStateException.class)
public void mvcMatcherServletPathRequired() throws Exception {
final ServletRegistration registration = mock(ServletRegistration.class);
when(registration.getMappings()).thenReturn(Collections.singleton("/spring"));
Answer<Map<String, ? extends ServletRegistration>> answer = new Answer<Map<String, ? extends ServletRegistration>>() {
@Override
public Map<String, ? extends ServletRegistration> answer(InvocationOnMock invocation) throws Throwable {
return Collections.<String, ServletRegistration>singletonMap("spring", registration);
}
};
when(servletContext.getServletRegistrations()).thenAnswer(answer);
loadConfig(MvcMatcherPathServletPathRequiredConfig.class);
}
@EnableWebSecurity
@Configuration
@EnableWebMvc
static class MvcMatcherPathServletPathRequiredConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.httpBasic().and()
.authorizeRequests()
.mvcMatchers("/user").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";
}
}
}
public void loadConfig(Class<?>... configs) {
this.context = new AnnotationConfigWebApplicationContext();
this.context.register(configs);
this.context.setServletContext(new MockServletContext());
this.context.setServletContext(this.servletContext);
this.context.refresh();
this.context.getAutowireCapableBeanFactory().autowireBean(this);
@@ -195,6 +195,71 @@ public class HttpSecurityRequestMatchersTests {
}
}
@Test
public void requestMatchersMvcMatcherServletPath() throws Exception {
loadConfig(RequestMatchersMvcMatcherServeltPathConfig.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);
}
@EnableWebSecurity
@Configuration
@EnableWebMvc
static class RequestMatchersMvcMatcherServeltPathConfig
extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.requestMatchers()
.mvcMatchers("/path").servletPath("/spring")
.mvcMatchers("/never-match")
.and()
.httpBasic().and()
.authorizeRequests()
.anyRequest().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";
}
}
}
public void loadConfig(Class<?>... configs) {
this.context = new AnnotationConfigWebApplicationContext();
this.context.register(configs);
@@ -0,0 +1,214 @@
/*
* Copyright 2002-2015 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.annotation.web.configurers;
import javax.servlet.http.HttpServletResponse;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
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.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.FilterChainProxy;
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;
/**
* @author Rob Winch
*
*/
public class UrlAuthorizationConfigurerTests {
AnnotationConfigWebApplicationContext context;
MockHttpServletRequest request;
MockHttpServletResponse response;
MockFilterChain chain;
@Autowired
FilterChainProxy springSecurityFilterChain;
@Before
public void setup() {
this.request = new MockHttpServletRequest();
this.request.setMethod("GET");
this.response = new MockHttpServletResponse();
this.chain = new MockFilterChain();
}
@After
public void cleanup() {
if (this.context != null) {
this.context.close();
}
}
@Test
public void mvcMatcher() throws Exception {
loadConfig(MvcMatcherConfig.class);
this.request.setRequestURI("/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus())
.isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setRequestURI("/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()
.apply(new UrlAuthorizationConfigurer(getApplicationContext())).getRegistry()
.mvcMatchers("/path").hasRole("ADMIN");
// @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 mvcMatcherServletPath() throws Exception {
loadConfig(MvcMatcherServletPathConfig.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("/spring");
this.request.setRequestURI("/spring/path.html");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus())
.isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
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("/foo");
this.request.setRequestURI("/foo/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
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);
}
@EnableWebSecurity
@Configuration
@EnableWebMvc
static class MvcMatcherServletPathConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.httpBasic().and()
.apply(new UrlAuthorizationConfigurer(getApplicationContext())).getRegistry()
.mvcMatchers("/path").servletPath("/spring").hasRole("ADMIN");
// @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);
}
}
@@ -15,11 +15,9 @@
*/
package org.springframework.security.config.http;
import java.util.Collection;
import org.junit.After;
import org.junit.Test;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.context.support.AbstractXmlApplicationContext;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
@@ -33,6 +31,8 @@ import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.expression.ExpressionBasedFilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
import java.util.Collection;
import static org.assertj.core.api.Assertions.assertThat;
/**
@@ -118,6 +118,13 @@ public class FilterSecurityMetadataSourceBeanDefinitionParserTests {
+ ConfigTestUtils.AUTH_PROVIDER_XML);
}
@Test(expected = BeanDefinitionParsingException.class)
public void parsingInterceptUrlServletPathFails() {
setContext("<filter-security-metadata-source id='fids' use-expressions='false'>"
+ " <intercept-url pattern='/secure' access='ROLE_USER' servlet-path='/spring' />"
+ "</filter-security-metadata-source>");
}
private FilterInvocation createFilterInvocation(String path, String method) {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI(null);