diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/builders/WebSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/web/builders/WebSecurity.java index 953d8f57758..e9231464240 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/builders/WebSecurity.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/builders/WebSecurity.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -16,15 +16,10 @@ package org.springframework.security.config.annotation.web.builders; -import java.util.ArrayList; -import java.util.List; - import jakarta.servlet.Filter; import jakarta.servlet.http.HttpServletRequest; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.beans.BeansException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.context.ApplicationContext; @@ -60,6 +55,9 @@ import org.springframework.util.Assert; import org.springframework.web.filter.DelegatingFilterProxy; +import java.util.ArrayList; +import java.util.List; + /** *

* The {@link WebSecurity} is created by {@link WebSecurityConfiguration} to create the @@ -268,6 +266,19 @@ public WebSecurity postBuildAction(Runnable postBuildAction) { return this; } + /** + * Sets the handler to handle + * {@link org.springframework.security.web.firewall.RequestRejectedException} + * @param requestRejectedHandler + * @return the {@link WebSecurity} for further customizations + * @since 5.7 + */ + public WebSecurity requestRejectedHandler(RequestRejectedHandler requestRejectedHandler) { + Assert.notNull(requestRejectedHandler, "requestRejectedHandler cannot be null"); + this.requestRejectedHandler = requestRejectedHandler; + return this; + } + @Override protected Filter performBuild() throws Exception { Assert.state(!this.securityFilterChainBuilders.isEmpty(), diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/builders/WebSecurityTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/builders/WebSecurityTests.java index 3076cb5d2ba..278f0bb6e25 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/builders/WebSecurityTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/builders/WebSecurityTests.java @@ -16,6 +16,7 @@ package org.springframework.security.config.annotation.web.builders; +import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.AfterEach; @@ -24,6 +25,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; import org.springframework.mock.web.MockFilterChain; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; @@ -32,6 +34,7 @@ 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.security.web.firewall.HttpStatusRequestRejectedHandler; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; @@ -39,6 +42,8 @@ import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import java.io.IOException; + import static org.assertj.core.api.Assertions.assertThat; /** @@ -92,6 +97,15 @@ public void ignoringMvcMatcher() throws Exception { assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); } + @Test + public void requestRejectedHandlerInvoked() throws ServletException, IOException { + loadConfig(RequestRejectedHandlerConfig.class); + this.request.setServletPath("/spring"); + this.request.setRequestURI("/spring/\u0019path"); + this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); + assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_BAD_REQUEST); + } + @Test public void ignoringMvcMatcherServletPath() throws Exception { loadConfig(MvcMatcherServletPathConfig.class, LegacyMvcMatchingConfig.class); @@ -223,4 +237,14 @@ public void configurePathMatch(PathMatchConfigurer configurer) { } + @EnableWebSecurity + static class RequestRejectedHandlerConfig extends WebSecurityConfigurerAdapter { + + @Override + public void configure(WebSecurity web) throws Exception { + web.requestRejectedHandler(new HttpStatusRequestRejectedHandler(HttpStatus.BAD_REQUEST.value())); + } + + } + } diff --git a/web/src/main/java/org/springframework/security/web/firewall/CompositeRequestRejectedHandler.java b/web/src/main/java/org/springframework/security/web/firewall/CompositeRequestRejectedHandler.java new file mode 100644 index 00000000000..bc7d658e653 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/firewall/CompositeRequestRejectedHandler.java @@ -0,0 +1,57 @@ +/* + * Copyright 2002-2021 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.firewall; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.util.Assert; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +/** + * A {@link RequestRejectedHandler} that delegates to several other + * {@link RequestRejectedHandler}s. + * + * @author Adam Ostrožlík + * @since 5.7 + */ +public final class CompositeRequestRejectedHandler implements RequestRejectedHandler { + + private final List requestRejectedhandlers; + + /** + * Creates a new instance. + * @param requestRejectedhandlers the {@link RequestRejectedHandler} instances to + * handle {@link org.springframework.security.web.firewall.RequestRejectedException} + */ + public CompositeRequestRejectedHandler(RequestRejectedHandler... requestRejectedhandlers) { + Assert.notEmpty(requestRejectedhandlers, "requestRejectedhandlers cannot be empty"); + this.requestRejectedhandlers = Arrays.asList(requestRejectedhandlers); + } + + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, + RequestRejectedException requestRejectedException) throws IOException, ServletException { + for (RequestRejectedHandler requestRejectedhandler : requestRejectedhandlers) { + requestRejectedhandler.handle(request, response, requestRejectedException); + } + } + +} diff --git a/web/src/test/java/org/springframework/security/web/firewall/CompositeRequestRejectedHandlerTests.java b/web/src/test/java/org/springframework/security/web/firewall/CompositeRequestRejectedHandlerTests.java new file mode 100644 index 00000000000..3868aad98ea --- /dev/null +++ b/web/src/test/java/org/springframework/security/web/firewall/CompositeRequestRejectedHandlerTests.java @@ -0,0 +1,43 @@ +/* + * Copyright 2002-2021 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.firewall; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockito.Mockito.mock; + +public class CompositeRequestRejectedHandlerTests { + + @Test + void compositeRequestRejectedHandlerRethrowsTheException() { + RequestRejectedException requestRejectedException = new RequestRejectedException("rejected"); + DefaultRequestRejectedHandler sut = new DefaultRequestRejectedHandler(); + CompositeRequestRejectedHandler crrh = new CompositeRequestRejectedHandler(sut); + assertThatExceptionOfType(RequestRejectedException.class).isThrownBy(() -> crrh + .handle(mock(HttpServletRequest.class), mock(HttpServletResponse.class), requestRejectedException)) + .withMessage("rejected"); + } + + @Test + void compositeRequestRejectedHandlerForbidsEmptyHandlers() { + assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(CompositeRequestRejectedHandler::new); + } + +}