Browse Source

Introduced DispatcherType request matcher

Created a DispatcherTypeRequestMatcher and corresponding methods
for configuring an HttpSecurity object. This enables filtering of
security rules based on the dispatcher type of the incoming servlet
request.

Closes gh-9205
Nick McKinney 4 years ago
parent
commit
6be25df1db

+ 34 - 0
config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.java

@@ -20,14 +20,18 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
+import javax.servlet.DispatcherType;
+
 import org.springframework.beans.factory.NoSuchBeanDefinitionException;
 import org.springframework.context.ApplicationContext;
 import org.springframework.http.HttpMethod;
+import org.springframework.lang.Nullable;
 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.DispatcherTypeRequestMatcher;
 import org.springframework.security.web.util.matcher.RegexRequestMatcher;
 import org.springframework.security.web.util.matcher.RequestMatcher;
 import org.springframework.util.Assert;
@@ -206,6 +210,36 @@ public abstract class AbstractRequestMatcherRegistry<C> {
 		return chainRequestMatchers(RequestMatchers.regexMatchers(regexPatterns));
 	}
 
+	/**
+	 * Maps a {@link List} of
+	 * {@link org.springframework.security.web.util.matcher.DispatcherTypeRequestMatcher}
+	 * instances.
+	 * @param method the {@link HttpMethod} to use or {@code null} for any
+	 * {@link HttpMethod}.
+	 * @param dispatcherTypes the dispatcher types to match against
+	 * @return the object that is chained after creating the {@link RequestMatcher}
+	 */
+	public C dispatcherTypeMatchers(@Nullable HttpMethod method, DispatcherType... dispatcherTypes) {
+		Assert.state(!this.anyRequestConfigured, "Can't configure dispatcherTypeMatchers after anyRequest");
+		List<RequestMatcher> matchers = new ArrayList<>();
+		for (DispatcherType dispatcherType : dispatcherTypes) {
+			matchers.add(new DispatcherTypeRequestMatcher(dispatcherType, method));
+		}
+		return chainRequestMatchers(matchers);
+	}
+
+	/**
+	 * Create a {@link List} of
+	 * {@link org.springframework.security.web.util.matcher.DispatcherTypeRequestMatcher}
+	 * instances that do not specify an {@link HttpMethod}.
+	 * @param dispatcherTypes the dispatcher types to match against
+	 * @return the object that is chained after creating the {@link RequestMatcher}
+	 */
+	public C dispatcherTypeMatchers(DispatcherType... dispatcherTypes) {
+		Assert.state(!this.anyRequestConfigured, "Can't configure dispatcherTypeMatchers after anyRequest");
+		return dispatcherTypeMatchers(null, dispatcherTypes);
+	}
+
 	/**
 	 * Associates a list of {@link RequestMatcher} instances with the
 	 * {@link AbstractConfigAttributeRequestMatcherRegistry}

+ 20 - 0
config/src/test/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistryTests.java

@@ -18,11 +18,14 @@ package org.springframework.security.config.annotation.web;
 
 import java.util.List;
 
+import javax.servlet.DispatcherType;
+
 import org.junit.Before;
 import org.junit.Test;
 
 import org.springframework.http.HttpMethod;
 import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
+import org.springframework.security.web.util.matcher.DispatcherTypeRequestMatcher;
 import org.springframework.security.web.util.matcher.RegexRequestMatcher;
 import org.springframework.security.web.util.matcher.RequestMatcher;
 
@@ -74,6 +77,23 @@ public class AbstractRequestMatcherRegistryTests {
 		assertThat(requestMatchers.get(0)).isExactlyInstanceOf(AntPathRequestMatcher.class);
 	}
 
+	@Test
+	public void dispatcherTypeMatchersWhenHttpMethodAndPatternParamsThenReturnAntPathRequestMatcherType() {
+		List<RequestMatcher> requestMatchers = this.matcherRegistry.dispatcherTypeMatchers(HttpMethod.GET,
+				DispatcherType.ASYNC);
+		assertThat(requestMatchers).isNotEmpty();
+		assertThat(requestMatchers.size()).isEqualTo(1);
+		assertThat(requestMatchers.get(0)).isExactlyInstanceOf(DispatcherTypeRequestMatcher.class);
+	}
+
+	@Test
+	public void dispatcherMatchersWhenPatternParamThenReturnAntPathRequestMatcherType() {
+		List<RequestMatcher> requestMatchers = this.matcherRegistry.dispatcherTypeMatchers(DispatcherType.INCLUDE);
+		assertThat(requestMatchers).isNotEmpty();
+		assertThat(requestMatchers.size()).isEqualTo(1);
+		assertThat(requestMatchers.get(0)).isExactlyInstanceOf(DispatcherTypeRequestMatcher.class);
+	}
+
 	private static class TestRequestMatcherRegistry extends AbstractRequestMatcherRegistry<List<RequestMatcher>> {
 
 		@Override

+ 81 - 0
web/src/main/java/org/springframework/security/web/util/matcher/DispatcherTypeRequestMatcher.java

@@ -0,0 +1,81 @@
+/*
+ * Copyright 2002-2020 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
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.security.web.util.matcher;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.http.HttpMethod;
+import org.springframework.lang.Nullable;
+import org.springframework.util.StringUtils;
+
+/**
+ * Checks the {@link DispatcherType} to decide whether to match a given request.
+ * {@code HttpServletRequest}.
+ *
+ * Can also be configured to match a specific HTTP method.
+ *
+ * @author Nick McKinney
+ * @since 5.5
+ */
+public class DispatcherTypeRequestMatcher implements RequestMatcher {
+
+	private final DispatcherType dispatcherType;
+
+	@Nullable
+	private final HttpMethod httpMethod;
+
+	/**
+	 * Creates an instance which matches requests with the provided {@link DispatcherType}
+	 * @param dispatcherType the type to match against
+	 */
+	public DispatcherTypeRequestMatcher(DispatcherType dispatcherType) {
+		this(dispatcherType, null);
+	}
+
+	/**
+	 * Creates an instance which matches requests with the provided {@link DispatcherType}
+	 * and {@link HttpMethod}
+	 * @param dispatcherType the type to match against
+	 * @param httpMethod the HTTP method to match. May be null to match all methods.
+	 */
+	public DispatcherTypeRequestMatcher(DispatcherType dispatcherType, @Nullable HttpMethod httpMethod) {
+		this.dispatcherType = dispatcherType;
+		this.httpMethod = httpMethod;
+	}
+
+	/**
+	 * Performs the match against the request's method and dispatcher type.
+	 * @param request the request to check for a match
+	 * @return true if the http method and dispatcher type align
+	 */
+	@Override
+	public boolean matches(HttpServletRequest request) {
+		if (this.httpMethod != null && StringUtils.hasText(request.getMethod())
+				&& this.httpMethod != HttpMethod.resolve(request.getMethod())) {
+			return false;
+		}
+		return this.dispatcherType == request.getDispatcherType();
+	}
+
+	@Override
+	public String toString() {
+		return "DispatcherTypeRequestMatcher{" + "dispatcherType=" + this.dispatcherType + ", httpMethod="
+				+ this.httpMethod + '}';
+	}
+
+}

+ 81 - 0
web/src/test/java/org/springframework/security/web/util/matcher/DispatcherTypeRequestMatcherTests.java

@@ -0,0 +1,81 @@
+/*
+ * Copyright 2002-2020 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
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.security.web.util.matcher;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.http.HttpServletRequest;
+
+import org.junit.Test;
+
+import org.springframework.http.HttpMethod;
+import org.springframework.mock.web.MockHttpServletRequest;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Nick McKinney
+ */
+public class DispatcherTypeRequestMatcherTests {
+
+	@Test
+	public void matches_dispatcher_type() {
+		HttpServletRequest request = mockHttpServletRequest(DispatcherType.ERROR, HttpMethod.GET);
+		DispatcherTypeRequestMatcher matcher = new DispatcherTypeRequestMatcher(DispatcherType.ERROR);
+
+		assertThat(matcher.matches(request)).isTrue();
+	}
+
+	@Test
+	public void matches_dispatcher_type_and_http_method() {
+		HttpServletRequest request = mockHttpServletRequest(DispatcherType.ERROR, HttpMethod.GET);
+		DispatcherTypeRequestMatcher matcher = new DispatcherTypeRequestMatcher(DispatcherType.ERROR, HttpMethod.GET);
+
+		assertThat(matcher.matches(request)).isTrue();
+	}
+
+	@Test
+	public void does_not_match_wrong_type() {
+		HttpServletRequest request = mockHttpServletRequest(DispatcherType.FORWARD, HttpMethod.GET);
+		DispatcherTypeRequestMatcher matcher = new DispatcherTypeRequestMatcher(DispatcherType.ERROR);
+
+		assertThat(matcher.matches(request)).isFalse();
+	}
+
+	@Test
+	public void does_not_match_with_wrong_http_method() {
+		HttpServletRequest request = mockHttpServletRequest(DispatcherType.ERROR, HttpMethod.GET);
+		DispatcherTypeRequestMatcher matcher = new DispatcherTypeRequestMatcher(DispatcherType.ERROR, HttpMethod.POST);
+
+		assertThat(matcher.matches(request)).isFalse();
+	}
+
+	@Test
+	public void null_http_method_matches_any_http_method() {
+		HttpServletRequest request = mockHttpServletRequest(DispatcherType.ERROR, HttpMethod.POST);
+		DispatcherTypeRequestMatcher matcher = new DispatcherTypeRequestMatcher(DispatcherType.ERROR, null);
+
+		assertThat(matcher.matches(request)).isTrue();
+	}
+
+	private HttpServletRequest mockHttpServletRequest(DispatcherType dispatcherType, HttpMethod httpMethod) {
+		MockHttpServletRequest mockHttpServletRequest = new MockHttpServletRequest();
+		mockHttpServletRequest.setDispatcherType(dispatcherType);
+		mockHttpServletRequest.setMethod(httpMethod.name());
+		return mockHttpServletRequest;
+	}
+
+}