MvcRequestMatcher servletPath / JavaConfig
Issue: gh-3987
This commit is contained in:
parent
050198e51b
commit
3befb1c8a6
|
@ -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);
|
||||
|
|
|
@ -4166,7 +4166,7 @@ Additionally, if the https://www.w3.org/TR/CSP2/#directive-report-uri[*_report-u
|
|||
then the violation will be reported by the user-agent to the declared URL.
|
||||
|
||||
For example, if a web application violates the declared security policy,
|
||||
the following response header will instruct the user-agent to send violation reports to the URL specified in the policy’s _report-uri_ directive.
|
||||
the following response header will instruct the user-agent to send violation reports to the URL specified in the policy's _report-uri_ directive.
|
||||
|
||||
[source]
|
||||
----
|
||||
|
@ -5786,7 +5786,7 @@ If CSRF protection is enabled, this tag inserts a hidden form field with the cor
|
|||
|
||||
Normally Spring Security automatically inserts a CSRF form field for any `<form:form>` tags you use, but if for some reason you cannot use `<form:form>`, `csrfInput` is a handy replacement.
|
||||
|
||||
You should place this tag within an HTML `<form></form>` block, where you would normally place other input fields. Do NOT place this tag within a Spring `<form:form></form:form>` block—Spring Security handles Spring forms automatically.
|
||||
You should place this tag within an HTML `<form></form>` block, where you would normally place other input fields. Do NOT place this tag within a Spring `<form:form></form:form>` block. Spring Security handles Spring forms automatically.
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
|
@ -7475,12 +7475,12 @@ 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 `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.
|
||||
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>>, <<nsa-intercept-url-method,method>> and <<nsa-intercept-url-servlet-path,servlet-path>> attributes. Ant paths are matched using an `AntPathRequestMatcher`, regular expressions are matched using a `RegexRequestMatcher` and for Spring MVC path matching the `MvcRequestMatcher` is used. 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]]
|
||||
* **request-matcher-ref**
|
||||
A referenece to a bean that implements `RequestMatcher` that will determine if this `FilterChain` should be used. This is a more powerful alternative to <<nsa-http-pattern,pattern>>.
|
||||
A reference to a bean that implements `RequestMatcher` that will determine if this `FilterChain` should be used. This is a more powerful alternative to <<nsa-http-pattern,pattern>>.
|
||||
|
||||
|
||||
[[nsa-http-security]]
|
||||
|
@ -8150,7 +8150,7 @@ Defines a reference to a Spring bean that implements `HttpFirewall`.
|
|||
|
||||
[[nsa-intercept-url]]
|
||||
==== <intercept-url>
|
||||
This element is used to define the set of URL patterns that the application is interested in and to configure how they should be handled. It is used to construct the `FilterInvocationSecurityMetadataSource` used by the `FilterSecurityInterceptor`. It is also responsible for configuring a `ChannelProcessingFilter` if particular URLs need to be accessed by HTTPS, for example. When matching the specified patterns against an incoming request, the matching is done in the order in which the elements are declared. So the most specific matches patterns should come first and the most general should come last.
|
||||
This element is used to define the set of URL patterns that the application is interested in and to configure how they should be handled. It is used to construct the `FilterInvocationSecurityMetadataSource` used by the `FilterSecurityInterceptor`. It is also responsible for configuring a `ChannelProcessingFilter` if particular URLs need to be accessed by HTTPS, for example. When matching the specified patterns against an incoming request, the matching is done in the order in which the elements are declared. So the most specific patterns should come first and the most general should come last.
|
||||
|
||||
|
||||
[[nsa-intercept-url-parents]]
|
||||
|
@ -8179,7 +8179,7 @@ NOTE: This property is invalid for <<nsa-filter-security-metadata-source,filter-
|
|||
|
||||
[[nsa-intercept-url-method]]
|
||||
* **method**
|
||||
The HTTP Method which will be used in combination with the pattern to match an incoming request. If omitted, any method will match. If an identical pattern is specified with and without a method, the method-specific match will take precedence.
|
||||
The HTTP Method which will be used in combination with the pattern and servlet path (optional) to match an incoming request. If omitted, any method will match. If an identical pattern is specified with and without a method, the method-specific match will take precedence.
|
||||
|
||||
|
||||
[[nsa-intercept-url-pattern]]
|
||||
|
@ -8195,6 +8195,12 @@ If a `<port-mappings>` configuration is added, this will be used to by the `Secu
|
|||
|
||||
NOTE: This property is invalid for <<nsa-filter-security-metadata-source,filter-security-metadata-source>>
|
||||
|
||||
[[nsa-intercept-url-servlet-path]]
|
||||
* **servlet-path**
|
||||
The servlet path which will be used in combination with the pattern and HTTP method to match an incoming request. This attribute is only applicable when <<nsa-http-request-matcher,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` `'/'`.
|
||||
|
||||
NOTE: This property is invalid for <<nsa-filter-security-metadata-source,filter-security-metadata-source>>
|
||||
|
||||
|
||||
[[nsa-jee]]
|
||||
==== <jee>
|
||||
|
@ -8699,7 +8705,7 @@ Used to explicitly configure a FilterChainProxy instance with a FilterChainMap
|
|||
|
||||
[[nsa-filter-chain-map-request-matcher]]
|
||||
* **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.
|
||||
Defines the strategy to 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.
|
||||
|
||||
|
||||
[[nsa-filter-chain-map-children]]
|
||||
|
@ -8734,7 +8740,7 @@ A comma separated list of references to Spring beans that implement `Filter`. Th
|
|||
|
||||
[[nsa-filter-chain-pattern]]
|
||||
* **pattern**
|
||||
A-pattern that creates RequestMatcher in combination with the <<nsa-filter-chain-map-request-matcher,request-matcher>>
|
||||
A pattern that creates RequestMatcher in combination with the <<nsa-filter-chain-map-request-matcher,request-matcher>>
|
||||
|
||||
|
||||
[[nsa-filter-chain-request-matcher-ref]]
|
||||
|
|
|
@ -16,11 +16,6 @@
|
|||
|
||||
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;
|
||||
|
@ -31,20 +26,32 @@ import org.springframework.web.servlet.handler.MatchableHandlerMapping;
|
|||
import org.springframework.web.servlet.handler.RequestMatchResult;
|
||||
import org.springframework.web.util.UrlPathHelper;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A {@link RequestMatcher} that uses Spring MVC's {@link HandlerMappingIntrospector} to
|
||||
* match the path and extract variables.
|
||||
*
|
||||
* <p>
|
||||
* It is important to understand that Spring MVC's matching is relative to the servlet
|
||||
* path. This means if you have mapped any servlet to a path that starts with "/" and is
|
||||
* greater than one, you should also specify the {@link #setServletPath(String)}
|
||||
* attribute to differentiate mappings.
|
||||
* </p>
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 4.1.1
|
||||
*/
|
||||
public final class MvcRequestMatcher
|
||||
public class MvcRequestMatcher
|
||||
implements RequestMatcher, RequestVariablesExtractor {
|
||||
private final DefaultMatcher defaultMatcher = new DefaultMatcher();
|
||||
|
||||
private final HandlerMappingIntrospector introspector;
|
||||
private final String pattern;
|
||||
private HttpMethod method;
|
||||
private String servletPath;
|
||||
|
||||
public MvcRequestMatcher(HandlerMappingIntrospector introspector, String pattern) {
|
||||
this.introspector = introspector;
|
||||
|
@ -56,6 +63,9 @@ public final class MvcRequestMatcher
|
|||
if (this.method != null && !this.method.name().equals(request.getMethod())) {
|
||||
return false;
|
||||
}
|
||||
if (this.servletPath != null && !this.servletPath.equals(request.getServletPath())) {
|
||||
return false;
|
||||
}
|
||||
MatchableHandlerMapping mapping = getMapping(request);
|
||||
if (mapping == null) {
|
||||
return this.defaultMatcher.matches(request);
|
||||
|
@ -97,6 +107,24 @@ public final class MvcRequestMatcher
|
|||
this.method = method;
|
||||
}
|
||||
|
||||
/**
|
||||
* The servlet path to match on. The default is undefined which means any servlet
|
||||
* path.
|
||||
*
|
||||
* @param servletPath the servletPath to set
|
||||
*/
|
||||
public void setServletPath(String servletPath) {
|
||||
this.servletPath = servletPath;
|
||||
}
|
||||
|
||||
protected final String getServletPath() {
|
||||
return this.servletPath;
|
||||
}
|
||||
|
||||
protected final String getMvcPattern() {
|
||||
return this.pattern;
|
||||
}
|
||||
|
||||
private class DefaultMatcher implements RequestMatcher, RequestVariablesExtractor {
|
||||
|
||||
private final UrlPathHelper pathHelper = new UrlPathHelper();
|
||||
|
@ -124,4 +152,4 @@ public final class MvcRequestMatcher
|
|||
return Collections.emptyMap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -108,6 +108,31 @@ public class MvcRequestMatcherTests {
|
|||
assertThat(this.matcher.extractUriTemplateVariables(this.request)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesServletPathTrue() throws Exception {
|
||||
when(this.introspector.getMatchableHandlerMapping(this.request))
|
||||
.thenReturn(this.mapping);
|
||||
when(this.mapping.match(eq(this.request), this.pattern.capture()))
|
||||
.thenReturn(this.result);
|
||||
this.matcher.setServletPath("/spring");
|
||||
this.request.setServletPath("/spring");
|
||||
|
||||
assertThat(this.matcher.matches(this.request)).isTrue();
|
||||
assertThat(this.pattern.getValue()).isEqualTo("/path");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesServletPathFalse() throws Exception {
|
||||
when(this.introspector.getMatchableHandlerMapping(this.request))
|
||||
.thenReturn(this.mapping);
|
||||
when(this.mapping.match(eq(this.request), this.pattern.capture()))
|
||||
.thenReturn(this.result);
|
||||
this.matcher.setServletPath("/spring");
|
||||
this.request.setServletPath("/");
|
||||
|
||||
assertThat(this.matcher.matches(this.request)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesPathOnlyTrue() throws Exception {
|
||||
when(this.introspector.getMatchableHandlerMapping(this.request))
|
||||
|
|
Loading…
Reference in New Issue