Add intercept-url@request-matcher-ref

Fixes gh-4097
This commit is contained in:
Rob Winch 2016-10-18 22:16:43 -05:00
parent f019ea89e7
commit af9139b613
8 changed files with 141 additions and 17 deletions

View File

@ -15,13 +15,16 @@
*/
package org.springframework.security.config.http;
import static org.springframework.security.config.http.HttpSecurityBeanDefinitionParser.ATT_REQUEST_MATCHER_REF;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Element;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedMap;
@ -99,7 +102,7 @@ public class FilterInvocationSecurityMetadataSourceParser implements BeanDefinit
MatcherType matcherType = MatcherType.fromElement(httpElt);
boolean useExpressions = isUseExpressions(httpElt);
ManagedMap<BeanDefinition, BeanDefinition> requestToAttributesMap = parseInterceptUrlsForFilterInvocationRequestMap(
ManagedMap<BeanMetadataElement, BeanDefinition> requestToAttributesMap = parseInterceptUrlsForFilterInvocationRequestMap(
matcherType, interceptUrls, useExpressions, addAllAuth, pc);
BeanDefinitionBuilder fidsBuilder;
@ -148,11 +151,11 @@ public class FilterInvocationSecurityMetadataSourceParser implements BeanDefinit
return !StringUtils.hasText(useExpressions) || "true".equals(useExpressions);
}
private static ManagedMap<BeanDefinition, BeanDefinition> parseInterceptUrlsForFilterInvocationRequestMap(
private static ManagedMap<BeanMetadataElement, BeanDefinition> parseInterceptUrlsForFilterInvocationRequestMap(
MatcherType matcherType, List<Element> urlElts, boolean useExpressions,
boolean addAuthenticatedAll, ParserContext parserContext) {
ManagedMap<BeanDefinition, BeanDefinition> filterInvocationDefinitionMap = new ManagedMap<BeanDefinition, BeanDefinition>();
ManagedMap<BeanMetadataElement, BeanDefinition> filterInvocationDefinitionMap = new ManagedMap<BeanMetadataElement, BeanDefinition>();
for (Element urlElt : urlElts) {
String access = urlElt.getAttribute(ATT_ACCESS);
@ -161,8 +164,10 @@ public class FilterInvocationSecurityMetadataSourceParser implements BeanDefinit
}
String path = urlElt.getAttribute(ATT_PATTERN);
String matcherRef = urlElt.getAttribute(ATT_REQUEST_MATCHER_REF);
boolean hasMatcherRef = StringUtils.hasText(matcherRef);
if (!StringUtils.hasText(path)) {
if (!hasMatcherRef && !StringUtils.hasText(path)) {
parserContext.getReaderContext().error(
"path attribute cannot be empty or null", urlElt);
}
@ -180,7 +185,7 @@ public class FilterInvocationSecurityMetadataSourceParser implements BeanDefinit
ATT_SERVLET_PATH + " is not applicable for request-matcher: '" + matcherType.name() + "'", urlElt);
}
BeanDefinition matcher = matcherType.createMatcher(parserContext, path,
BeanMetadataElement matcher = hasMatcherRef ? new RuntimeBeanReference(matcherRef) : matcherType.createMatcher(parserContext, path,
method, servletPath);
BeanDefinitionBuilder attributeBuilder = BeanDefinitionBuilder
.rootBeanDefinition(SecurityConfig.class);

View File

@ -80,6 +80,7 @@ import org.springframework.util.xml.DomUtils;
import static org.springframework.security.config.http.HttpSecurityBeanDefinitionParser.ATT_FILTERS;
import static org.springframework.security.config.http.HttpSecurityBeanDefinitionParser.ATT_HTTP_METHOD;
import static org.springframework.security.config.http.HttpSecurityBeanDefinitionParser.ATT_PATH_PATTERN;
import static org.springframework.security.config.http.HttpSecurityBeanDefinitionParser.ATT_REQUEST_MATCHER_REF;
import static org.springframework.security.config.http.HttpSecurityBeanDefinitionParser.ATT_REQUIRES_CHANNEL;
import static org.springframework.security.config.http.SecurityFilters.CHANNEL_FILTER;
import static org.springframework.security.config.http.SecurityFilters.CONCURRENT_SESSION_FILTER;
@ -582,7 +583,7 @@ class HttpConfigurationBuilder {
}
private void createChannelProcessingFilter() {
ManagedMap<BeanDefinition, BeanDefinition> channelRequestMap = parseInterceptUrlsForChannelSecurity();
ManagedMap<BeanMetadataElement, BeanDefinition> channelRequestMap = parseInterceptUrlsForChannelSecurity();
if (channelRequestMap.isEmpty()) {
return;
@ -636,15 +637,17 @@ class HttpConfigurationBuilder {
* will be empty unless the <tt>requires-channel</tt> attribute has been used on a URL
* path.
*/
private ManagedMap<BeanDefinition, BeanDefinition> parseInterceptUrlsForChannelSecurity() {
private ManagedMap<BeanMetadataElement, BeanDefinition> parseInterceptUrlsForChannelSecurity() {
ManagedMap<BeanDefinition, BeanDefinition> channelRequestMap = new ManagedMap<BeanDefinition, BeanDefinition>();
ManagedMap<BeanMetadataElement, BeanDefinition> channelRequestMap = new ManagedMap<BeanMetadataElement, BeanDefinition>();
for (Element urlElt : interceptUrls) {
String path = urlElt.getAttribute(ATT_PATH_PATTERN);
String method = urlElt.getAttribute(ATT_HTTP_METHOD);
String matcherRef = urlElt.getAttribute(ATT_REQUEST_MATCHER_REF);
boolean hasMatcherRef = StringUtils.hasText(matcherRef);
if (!StringUtils.hasText(path)) {
if (!hasMatcherRef && !StringUtils.hasText(path)) {
pc.getReaderContext().error("pattern attribute cannot be empty or null",
urlElt);
}
@ -652,7 +655,7 @@ class HttpConfigurationBuilder {
String requiredChannel = urlElt.getAttribute(ATT_REQUIRES_CHANNEL);
if (StringUtils.hasText(requiredChannel)) {
BeanDefinition matcher = matcherType.createMatcher(pc, path, method);
BeanMetadataElement matcher = hasMatcherRef ? new RuntimeBeanReference(matcherRef) : matcherType.createMatcher(pc, path, method);
RootBeanDefinition channelAttributes = new RootBeanDefinition(
ChannelAttributeFactory.class);

View File

@ -60,7 +60,7 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
.getLog(HttpSecurityBeanDefinitionParser.class);
private static final String ATT_AUTHENTICATION_MANAGER_REF = "authentication-manager-ref";
private static final String ATT_REQUEST_MATCHER_REF = "request-matcher-ref";
static final String ATT_REQUEST_MATCHER_REF = "request-matcher-ref";
static final String ATT_PATH_PATTERN = "pattern";
static final String ATT_HTTP_METHOD = "method";

View File

@ -366,8 +366,7 @@ intercept-url =
## Specifies the access attributes and/or filter list for a particular set of URLs.
element intercept-url {intercept-url.attlist, empty}
intercept-url.attlist &=
## The pattern which defines the URL path. The content will depend on the type set in the containing http element, so will default to ant path syntax.
attribute pattern {xsd:token}
(pattern | request-matcher-ref)
intercept-url.attlist &=
## The access configuration attributes that apply for the configured path.
attribute access {xsd:token}?

View File

@ -1292,10 +1292,15 @@
</xs:attributeGroup>
<xs:attributeGroup name="intercept-url.attlist">
<xs:attribute name="pattern" use="required" type="xs:token">
<xs:attribute name="pattern" type="xs:token">
<xs:annotation>
<xs:documentation>The pattern which defines the URL path. The content will depend on the type set in the
containing http element, so will default to ant path syntax.
<xs:documentation>The request URL pattern which will be mapped to the FilterChain.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="request-matcher-ref" type="xs:token">
<xs:annotation>
<xs:documentation>Allows a RequestMatcher instance to be used, as an alternative to pattern-matching.
</xs:documentation>
</xs:annotation>
</xs:attribute>

View File

@ -0,0 +1,85 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.http;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import javax.servlet.Filter;
import org.junit.After;
import org.junit.Test;
import org.springframework.mock.web.MockServletContext;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.web.context.support.XmlWebApplicationContext;
public class HttpInterceptUrlTests {
ConfigurableWebApplicationContext context;
MockMvc mockMvc;
@After
public void close() {
if(context != null) {
context.close();
}
}
@Test
public void interceptUrlWhenRequestMatcherRefThenWorks() throws Exception {
loadConfig("interceptUrlWhenRequestMatcherRefThenWorks.xml");
mockMvc.perform(get("/foo"))
.andExpect(status().isUnauthorized());
mockMvc.perform(get("/FOO"))
.andExpect(status().isUnauthorized());
mockMvc.perform(get("/other"))
.andExpect(status().isOk());
}
private void loadConfig(String... configLocations) {
for(int i=0;i<configLocations.length;i++) {
configLocations[i] = getClass().getName().replaceAll("\\.", "/") + "-" + configLocations[i];
}
XmlWebApplicationContext context = new XmlWebApplicationContext();
context.setConfigLocations(configLocations);
context.setServletContext(new MockServletContext());
context.refresh();
this.context = context;
context.getAutowireCapableBeanFactory().autowireBean(this);
Filter springSecurityFilterChain = context.getBean("springSecurityFilterChain", Filter.class);
mockMvc = MockMvcBuilders
.standaloneSetup(new FooController())
.addFilters(springSecurityFilterChain)
.build();
}
@RestController
static class FooController {
@GetMapping("/*")
String foo() {
return "foo";
}
}
}

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<http>
<http-basic/>
<intercept-url request-matcher-ref="matcherRef" access="denyAll"/>
</http>
<user-service>
<user name="user" password="password" authorities="ROLE_USER"/>
</user-service>
<b:bean id="matcherRef" class="org.springframework.security.web.util.matcher.AntPathRequestMatcher"
c:pattern="/foo"
c:httpMethod="GET"
c:caseSensitive="false"/>
</b:beans>

View File

@ -8197,6 +8197,11 @@ The HTTP Method which will be used in combination with the pattern and servlet p
The pattern which defines the URL path. The content will depend on the `request-matcher` attribute from the containing http element, so will default to ant path syntax.
[[nsa-intercept-url-request-matcher-ref]]
* **request-matcher-ref**
A reference to a `RequestMatcher` that will be used to determine if this `<intercept-url>` is used.
[[nsa-intercept-url-requires-channel]]
* **requires-channel**
Can be "http" or "https" depending on whether a particular URL pattern should be accessed over HTTP or HTTPS respectively. Alternatively the value "any" can be used when there is no preference. If this attribute is present on any `<intercept-url>` element, then a `ChannelProcessingFilter` will be added to the filter stack and its additional dependencies added to the application context.