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 e08b1eae97..62ccc42043 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
@@ -49,8 +49,9 @@ import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.debug.DebugFilter;
-import org.springframework.security.web.firewall.StrictHttpFirewall;
import org.springframework.security.web.firewall.HttpFirewall;
+import org.springframework.security.web.firewall.RequestRejectedHandler;
+import org.springframework.security.web.firewall.StrictHttpFirewall;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
@@ -91,6 +92,8 @@ public final class WebSecurity extends
private HttpFirewall httpFirewall;
+ private RequestRejectedHandler requestRejectedHandler;
+
private boolean debugEnabled;
private WebInvocationPrivilegeEvaluator privilegeEvaluator;
@@ -295,6 +298,9 @@ public final class WebSecurity extends
if (httpFirewall != null) {
filterChainProxy.setFirewall(httpFirewall);
}
+ if (requestRejectedHandler != null) {
+ filterChainProxy.setRequestRejectedHandler(requestRejectedHandler);
+ }
filterChainProxy.afterPropertiesSet();
Filter result = filterChainProxy;
@@ -392,5 +398,8 @@ public final class WebSecurity extends
try {
this.httpFirewall = applicationContext.getBean(HttpFirewall.class);
} catch(NoSuchBeanDefinitionException e) {}
+ try {
+ this.requestRejectedHandler = applicationContext.getBean(RequestRejectedHandler.class);
+ } catch(NoSuchBeanDefinitionException e) {}
}
}
diff --git a/web/src/main/java/org/springframework/security/web/FilterChainProxy.java b/web/src/main/java/org/springframework/security/web/FilterChainProxy.java
index d0d348b751..37019e3204 100644
--- a/web/src/main/java/org/springframework/security/web/FilterChainProxy.java
+++ b/web/src/main/java/org/springframework/security/web/FilterChainProxy.java
@@ -19,11 +19,15 @@ package org.springframework.security.web;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.firewall.DefaultRequestRejectedHandler;
import org.springframework.security.web.firewall.FirewalledRequest;
import org.springframework.security.web.firewall.HttpFirewall;
+import org.springframework.security.web.firewall.RequestRejectedException;
+import org.springframework.security.web.firewall.RequestRejectedHandler;
import org.springframework.security.web.firewall.StrictHttpFirewall;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.security.web.util.UrlUtils;
+import org.springframework.util.Assert;
import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.filter.GenericFilterBean;
@@ -149,6 +153,8 @@ public class FilterChainProxy extends GenericFilterBean {
private HttpFirewall firewall = new StrictHttpFirewall();
+ private RequestRejectedHandler requestRejectedHandler = new DefaultRequestRejectedHandler();
+
// ~ Methods
// ========================================================================================================
@@ -176,6 +182,8 @@ public class FilterChainProxy extends GenericFilterBean {
try {
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
doFilterInternal(request, response, chain);
+ } catch (RequestRejectedException e) {
+ requestRejectedHandler.handle((HttpServletRequest) request, (HttpServletResponse) response, e);
}
finally {
SecurityContextHolder.clearContext();
@@ -272,6 +280,17 @@ public class FilterChainProxy extends GenericFilterBean {
this.firewall = firewall;
}
+ /**
+ * Sets the {@link RequestRejectedHandler} to be used for requests rejected by the firewall.
+ *
+ * @since 5.2
+ * @param requestRejectedHandler the {@link RequestRejectedHandler}
+ */
+ public void setRequestRejectedHandler(RequestRejectedHandler requestRejectedHandler) {
+ Assert.notNull(requestRejectedHandler, "requestRejectedHandler may not be null");
+ this.requestRejectedHandler = requestRejectedHandler;
+ }
+
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
diff --git a/web/src/main/java/org/springframework/security/web/firewall/DefaultRequestRejectedHandler.java b/web/src/main/java/org/springframework/security/web/firewall/DefaultRequestRejectedHandler.java
new file mode 100644
index 0000000000..ef63e9f7d0
--- /dev/null
+++ b/web/src/main/java/org/springframework/security/web/firewall/DefaultRequestRejectedHandler.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2002-2018 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 java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Default implementation of {@link RequestRejectedHandler} that simply rethrows the exception.
+ *
+ * @author Leonard Brünings
+ * @since 5.2
+ */
+public class DefaultRequestRejectedHandler implements RequestRejectedHandler {
+ @Override
+ public void handle(HttpServletRequest request, HttpServletResponse response,
+ RequestRejectedException requestRejectedException) throws IOException, ServletException {
+ throw requestRejectedException;
+ }
+}
diff --git a/web/src/main/java/org/springframework/security/web/firewall/HttpStatusRequestRejectedHandler.java b/web/src/main/java/org/springframework/security/web/firewall/HttpStatusRequestRejectedHandler.java
new file mode 100644
index 0000000000..06c6ba5eaf
--- /dev/null
+++ b/web/src/main/java/org/springframework/security/web/firewall/HttpStatusRequestRejectedHandler.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2002-2018 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 java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * A simple implementation of {@link RequestRejectedHandler} that sends an error with configurable status code.
+ *
+ * @author Leonard Brünings
+ * @since 5.2
+ */
+public class HttpStatusRequestRejectedHandler implements RequestRejectedHandler {
+ private static final Log logger = LogFactory.getLog(HttpStatusRequestRejectedHandler.class);
+
+ private final int httpError;
+
+ /**
+ * Constructs an instance which uses {@code 400} as response code.
+ */
+ public HttpStatusRequestRejectedHandler() {
+ httpError = HttpServletResponse.SC_BAD_REQUEST;
+ }
+
+ /**
+ * Constructs an instance which uses a configurable http code as response.
+ * @param httpError http status code to use
+ */
+ public HttpStatusRequestRejectedHandler(int httpError) {
+ this.httpError = httpError;
+ }
+
+ @Override
+ public void handle(HttpServletRequest request, HttpServletResponse response,
+ RequestRejectedException requestRejectedException) throws IOException {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Rejecting request due to: " + requestRejectedException.getMessage(),
+ requestRejectedException);
+ }
+ response.sendError(httpError);
+ }
+}
diff --git a/web/src/main/java/org/springframework/security/web/firewall/RequestRejectedHandler.java b/web/src/main/java/org/springframework/security/web/firewall/RequestRejectedHandler.java
new file mode 100644
index 0000000000..f26afa5398
--- /dev/null
+++ b/web/src/main/java/org/springframework/security/web/firewall/RequestRejectedHandler.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2002-2018 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 java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Used by {@link org.springframework.security.web.FilterChainProxy} to handle an
+ * RequestRejectedException
.
+ *
+ * @author Leonard Brünings
+ * @since 5.2
+ */
+public interface RequestRejectedHandler {
+ // ~ Methods
+ // ========================================================================================================
+
+ /**
+ * Handles an request rejected failure.
+ *
+ * @param request that resulted in an RequestRejectedException
+ * @param response so that the user agent can be advised of the failure
+ * @param requestRejectedException that caused the invocation
+ *
+ * @throws IOException in the event of an IOException
+ * @throws ServletException in the event of a ServletException
+ */
+ void handle(HttpServletRequest request, HttpServletResponse response,
+ RequestRejectedException requestRejectedException) throws IOException,
+ ServletException;
+}
diff --git a/web/src/test/java/org/springframework/security/web/FilterChainProxyTests.java b/web/src/test/java/org/springframework/security/web/FilterChainProxyTests.java
index c16afe6a21..10067d6fd8 100644
--- a/web/src/test/java/org/springframework/security/web/FilterChainProxyTests.java
+++ b/web/src/test/java/org/springframework/security/web/FilterChainProxyTests.java
@@ -28,6 +28,8 @@ import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.firewall.FirewalledRequest;
import org.springframework.security.web.firewall.HttpFirewall;
+import org.springframework.security.web.firewall.RequestRejectedException;
+import org.springframework.security.web.firewall.RequestRejectedHandler;
import org.springframework.security.web.util.matcher.RequestMatcher;
import javax.servlet.Filter;
@@ -243,4 +245,21 @@ public class FilterChainProxyTests {
any(HttpServletResponse.class));
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
}
+
+ @Test(expected = IllegalArgumentException.class)
+ public void setRequestRejectedHandlerDoesNotAcceptNull() {
+ fcp.setRequestRejectedHandler(null);
+ }
+
+ @Test
+ public void requestRejectedHandlerIsCalledIfFirewallThrowsRequestRejectedException() throws Exception {
+ HttpFirewall fw = mock(HttpFirewall.class);
+ RequestRejectedHandler rjh = mock(RequestRejectedHandler.class);
+ fcp.setFirewall(fw);
+ fcp.setRequestRejectedHandler(rjh);
+ RequestRejectedException requestRejectedException = new RequestRejectedException("Contains illegal chars");
+ when(fw.getFirewalledRequest(request)).thenThrow(requestRejectedException);
+ fcp.doFilter(request, response, chain);
+ verify(rjh).handle(eq(request), eq(response), eq((requestRejectedException)));
+ }
}
diff --git a/web/src/test/java/org/springframework/security/web/firewall/DefaultRequestRejectedHandlerTest.java b/web/src/test/java/org/springframework/security/web/firewall/DefaultRequestRejectedHandlerTest.java
new file mode 100644
index 0000000000..ce245637d9
--- /dev/null
+++ b/web/src/test/java/org/springframework/security/web/firewall/DefaultRequestRejectedHandlerTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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
+ *
+ * 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 static org.mockito.Mockito.mock;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.hamcrest.CoreMatchers;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class DefaultRequestRejectedHandlerTest {
+
+ @Test
+ public void defaultRequestRejectedHandlerRethrowsTheException() throws Exception {
+ // given:
+ RequestRejectedException requestRejectedException = new RequestRejectedException("rejected");
+ DefaultRequestRejectedHandler sut = new DefaultRequestRejectedHandler();
+
+ //when:
+ try {
+ sut.handle(mock(HttpServletRequest.class), mock(HttpServletResponse.class), requestRejectedException);
+ } catch (RequestRejectedException exception) {
+ //then:
+ Assert.assertThat(exception.getMessage(), CoreMatchers.is("rejected"));
+ return;
+ }
+ Assert.fail("Exception was not rethrown");
+ }
+}
diff --git a/web/src/test/java/org/springframework/security/web/firewall/HttpStatusRequestRejectedHandlerTest.java b/web/src/test/java/org/springframework/security/web/firewall/HttpStatusRequestRejectedHandlerTest.java
new file mode 100644
index 0000000000..be6f9454ec
--- /dev/null
+++ b/web/src/test/java/org/springframework/security/web/firewall/HttpStatusRequestRejectedHandlerTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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
+ *
+ * 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 static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.junit.Test;
+
+public class HttpStatusRequestRejectedHandlerTest {
+
+ @Test
+ public void httpStatusRequestRejectedHandlerUsesStatus400byDefault() throws Exception {
+ //given:
+ HttpStatusRequestRejectedHandler sut = new HttpStatusRequestRejectedHandler();
+ HttpServletResponse response = mock(HttpServletResponse.class);
+
+ //when:
+ sut.handle(mock(HttpServletRequest.class), response, mock(RequestRejectedException.class));
+
+ // then:
+ verify(response).sendError(400);
+ }
+
+ @Test
+ public void httpStatusRequestRejectedHandlerCanBeConfiguredToUseStatus() throws Exception {
+ httpStatusRequestRejectedHandlerCanBeConfiguredToUseStatusHelper(400);
+ httpStatusRequestRejectedHandlerCanBeConfiguredToUseStatusHelper(403);
+ httpStatusRequestRejectedHandlerCanBeConfiguredToUseStatusHelper(500);
+ }
+
+ private void httpStatusRequestRejectedHandlerCanBeConfiguredToUseStatusHelper(int status) throws Exception {
+
+ //given:
+ HttpStatusRequestRejectedHandler sut = new HttpStatusRequestRejectedHandler(status);
+ HttpServletResponse response = mock(HttpServletResponse.class);
+
+ //when:
+ sut.handle(mock(HttpServletRequest.class), response, mock(RequestRejectedException.class));
+
+ // then:
+ verify(response).sendError(status);
+ }
+}