فهرست منبع

Add intercept-url@request-matcher-ref

Fixes gh-4097
Rob Winch 8 سال پیش
والد
کامیت
af9139b613

+ 11 - 6
config/src/main/java/org/springframework/security/config/http/FilterInvocationSecurityMetadataSourceParser.java

@@ -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);

+ 8 - 5
config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java

@@ -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);

+ 1 - 1
config/src/main/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParser.java

@@ -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";
 

+ 1 - 2
config/src/main/resources/org/springframework/security/config/spring-security-4.2.rnc

@@ -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}?

+ 8 - 3
config/src/main/resources/org/springframework/security/config/spring-security-4.2.xsd

@@ -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>

+ 85 - 0
config/src/test/java/org/springframework/security/config/http/HttpInterceptUrlTests.java

@@ -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";
+		}
+	}
+}

+ 22 - 0
config/src/test/resources/org/springframework/security/config/http/HttpInterceptUrlTests-interceptUrlWhenRequestMatcherRefThenWorks.xml

@@ -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>

+ 5 - 0
docs/manual/src/docs/asciidoc/index.adoc

@@ -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.