2
0
Эх сурвалжийг харах

Add RequestRejectedHandler

Closes gh-5007
Leonard Brünings 5 жил өмнө
parent
commit
b826c798f7

+ 10 - 1
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) {}
 	}
 }

+ 19 - 0
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();

+ 36 - 0
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;
+	}
+}

+ 61 - 0
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);
+	}
+}

+ 48 - 0
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
+ * <code>RequestRejectedException</code>.
+ *
+ * @author Leonard Brünings
+ * @since 5.2
+ */
+public interface RequestRejectedHandler {
+	// ~ Methods
+	// ========================================================================================================
+
+	/**
+	 * Handles an request rejected failure.
+	 *
+	 * @param request that resulted in an <code>RequestRejectedException</code>
+	 * @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;
+}

+ 19 - 0
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)));
+	}
 }

+ 46 - 0
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");
+	}
+}

+ 61 - 0
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);
+	}
+}