diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.java b/config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.java index e20d71054de..4f2135a3468 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.java @@ -20,14 +20,18 @@ 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; @@ -83,7 +87,7 @@ public C anyRequest() { * @return the object that is chained after creating the {@link RequestMatcher} */ public C antMatchers(HttpMethod method) { - return antMatchers(method, new String[] { "/**" }); + return antMatchers(method, "/**"); } /** @@ -206,6 +210,36 @@ public C regexMatchers(String... regexPatterns) { 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 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} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistryTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistryTests.java index ef2b7930244..9e6b57d57c1 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistryTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistryTests.java @@ -18,11 +18,14 @@ 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 void antMatchersWhenPatternParamThenReturnAntPathRequestMatcherType() { assertThat(requestMatchers.get(0)).isExactlyInstanceOf(AntPathRequestMatcher.class); } + @Test + public void dispatcherTypeMatchersWhenHttpMethodAndPatternParamsThenReturnAntPathRequestMatcherType() { + List 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 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> { @Override diff --git a/web/src/main/java/org/springframework/security/web/util/matcher/AntPathRequestMatcher.java b/web/src/main/java/org/springframework/security/web/util/matcher/AntPathRequestMatcher.java index 9e2968a03f8..8e66cceba7e 100644 --- a/web/src/main/java/org/springframework/security/web/util/matcher/AntPathRequestMatcher.java +++ b/web/src/main/java/org/springframework/security/web/util/matcher/AntPathRequestMatcher.java @@ -141,7 +141,7 @@ public AntPathRequestMatcher(String pattern, String httpMethod, boolean caseSens @Override public boolean matches(HttpServletRequest request) { if (this.httpMethod != null && StringUtils.hasText(request.getMethod()) - && this.httpMethod != valueOf(request.getMethod())) { + && this.httpMethod != HttpMethod.resolve(request.getMethod())) { return false; } if (this.pattern.equals(MATCH_ALL)) { @@ -211,21 +211,6 @@ public String toString() { return sb.toString(); } - /** - * Provides a save way of obtaining the HttpMethod from a String. If the method is - * invalid, returns null. - * @param method the HTTP method to use. - * @return the HttpMethod or null if method is invalid. - */ - private static HttpMethod valueOf(String method) { - try { - return HttpMethod.valueOf(method); - } - catch (IllegalArgumentException ex) { - return null; - } - } - private interface Matcher { boolean matches(String path); diff --git a/web/src/main/java/org/springframework/security/web/util/matcher/DispatcherTypeRequestMatcher.java b/web/src/main/java/org/springframework/security/web/util/matcher/DispatcherTypeRequestMatcher.java new file mode 100644 index 00000000000..617fc956fdf --- /dev/null +++ b/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 + '}'; + } + +} diff --git a/web/src/main/java/org/springframework/security/web/util/matcher/RegexRequestMatcher.java b/web/src/main/java/org/springframework/security/web/util/matcher/RegexRequestMatcher.java index 6d0bc19a1a9..9264b56f213 100644 --- a/web/src/main/java/org/springframework/security/web/util/matcher/RegexRequestMatcher.java +++ b/web/src/main/java/org/springframework/security/web/util/matcher/RegexRequestMatcher.java @@ -81,7 +81,8 @@ public RegexRequestMatcher(String pattern, String httpMethod, boolean caseInsens */ @Override public boolean matches(HttpServletRequest request) { - if (this.httpMethod != null && request.getMethod() != null && this.httpMethod != valueOf(request.getMethod())) { + if (this.httpMethod != null && request.getMethod() != null + && this.httpMethod != HttpMethod.resolve(request.getMethod())) { return false; } String url = request.getServletPath(); @@ -101,21 +102,6 @@ public boolean matches(HttpServletRequest request) { return this.pattern.matcher(url).matches(); } - /** - * Provides a save way of obtaining the HttpMethod from a String. If the method is - * invalid, returns null. - * @param method the HTTP method to use. - * @return the HttpMethod or null if method is invalid. - */ - private static HttpMethod valueOf(String method) { - try { - return HttpMethod.valueOf(method); - } - catch (IllegalArgumentException ex) { - return null; - } - } - @Override public String toString() { StringBuilder sb = new StringBuilder(); diff --git a/web/src/test/java/org/springframework/security/web/util/matcher/DispatcherTypeRequestMatcherTests.java b/web/src/test/java/org/springframework/security/web/util/matcher/DispatcherTypeRequestMatcherTests.java new file mode 100644 index 00000000000..d8a7be6ee39 --- /dev/null +++ b/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; + } + +}