Skip to content

Commit b826c79

Browse files
leonard84rwinch
authored andcommitted
Add RequestRejectedHandler
Closes gh-5007
1 parent a783fbc commit b826c79

File tree

8 files changed

+300
-1
lines changed

8 files changed

+300
-1
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/builders/WebSecurity.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,9 @@
4949
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
5050
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
5151
import org.springframework.security.web.debug.DebugFilter;
52-
import org.springframework.security.web.firewall.StrictHttpFirewall;
5352
import org.springframework.security.web.firewall.HttpFirewall;
53+
import org.springframework.security.web.firewall.RequestRejectedHandler;
54+
import org.springframework.security.web.firewall.StrictHttpFirewall;
5455
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
5556
import org.springframework.security.web.util.matcher.RequestMatcher;
5657
import org.springframework.util.Assert;
@@ -91,6 +92,8 @@ public final class WebSecurity extends
9192

9293
private HttpFirewall httpFirewall;
9394

95+
private RequestRejectedHandler requestRejectedHandler;
96+
9497
private boolean debugEnabled;
9598

9699
private WebInvocationPrivilegeEvaluator privilegeEvaluator;
@@ -295,6 +298,9 @@ protected Filter performBuild() throws Exception {
295298
if (httpFirewall != null) {
296299
filterChainProxy.setFirewall(httpFirewall);
297300
}
301+
if (requestRejectedHandler != null) {
302+
filterChainProxy.setRequestRejectedHandler(requestRejectedHandler);
303+
}
298304
filterChainProxy.afterPropertiesSet();
299305

300306
Filter result = filterChainProxy;
@@ -392,5 +398,8 @@ public void setApplicationContext(ApplicationContext applicationContext)
392398
try {
393399
this.httpFirewall = applicationContext.getBean(HttpFirewall.class);
394400
} catch(NoSuchBeanDefinitionException e) {}
401+
try {
402+
this.requestRejectedHandler = applicationContext.getBean(RequestRejectedHandler.class);
403+
} catch(NoSuchBeanDefinitionException e) {}
395404
}
396405
}

web/src/main/java/org/springframework/security/web/FilterChainProxy.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,15 @@
1919
import org.apache.commons.logging.Log;
2020
import org.apache.commons.logging.LogFactory;
2121
import org.springframework.security.core.context.SecurityContextHolder;
22+
import org.springframework.security.web.firewall.DefaultRequestRejectedHandler;
2223
import org.springframework.security.web.firewall.FirewalledRequest;
2324
import org.springframework.security.web.firewall.HttpFirewall;
25+
import org.springframework.security.web.firewall.RequestRejectedException;
26+
import org.springframework.security.web.firewall.RequestRejectedHandler;
2427
import org.springframework.security.web.firewall.StrictHttpFirewall;
2528
import org.springframework.security.web.util.matcher.RequestMatcher;
2629
import org.springframework.security.web.util.UrlUtils;
30+
import org.springframework.util.Assert;
2731
import org.springframework.web.filter.DelegatingFilterProxy;
2832
import org.springframework.web.filter.GenericFilterBean;
2933

@@ -149,6 +153,8 @@ public class FilterChainProxy extends GenericFilterBean {
149153

150154
private HttpFirewall firewall = new StrictHttpFirewall();
151155

156+
private RequestRejectedHandler requestRejectedHandler = new DefaultRequestRejectedHandler();
157+
152158
// ~ Methods
153159
// ========================================================================================================
154160

@@ -176,6 +182,8 @@ public void doFilter(ServletRequest request, ServletResponse response,
176182
try {
177183
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
178184
doFilterInternal(request, response, chain);
185+
} catch (RequestRejectedException e) {
186+
requestRejectedHandler.handle((HttpServletRequest) request, (HttpServletResponse) response, e);
179187
}
180188
finally {
181189
SecurityContextHolder.clearContext();
@@ -272,6 +280,17 @@ public void setFirewall(HttpFirewall firewall) {
272280
this.firewall = firewall;
273281
}
274282

283+
/**
284+
* Sets the {@link RequestRejectedHandler} to be used for requests rejected by the firewall.
285+
*
286+
* @since 5.2
287+
* @param requestRejectedHandler the {@link RequestRejectedHandler}
288+
*/
289+
public void setRequestRejectedHandler(RequestRejectedHandler requestRejectedHandler) {
290+
Assert.notNull(requestRejectedHandler, "requestRejectedHandler may not be null");
291+
this.requestRejectedHandler = requestRejectedHandler;
292+
}
293+
275294
@Override
276295
public String toString() {
277296
StringBuilder sb = new StringBuilder();
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2002-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.security.web.firewall;
17+
18+
import java.io.IOException;
19+
20+
import javax.servlet.ServletException;
21+
import javax.servlet.http.HttpServletRequest;
22+
import javax.servlet.http.HttpServletResponse;
23+
24+
/**
25+
* Default implementation of {@link RequestRejectedHandler} that simply rethrows the exception.
26+
*
27+
* @author Leonard Brünings
28+
* @since 5.2
29+
*/
30+
public class DefaultRequestRejectedHandler implements RequestRejectedHandler {
31+
@Override
32+
public void handle(HttpServletRequest request, HttpServletResponse response,
33+
RequestRejectedException requestRejectedException) throws IOException, ServletException {
34+
throw requestRejectedException;
35+
}
36+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright 2002-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.security.web.firewall;
17+
18+
import java.io.IOException;
19+
20+
import javax.servlet.http.HttpServletRequest;
21+
import javax.servlet.http.HttpServletResponse;
22+
23+
import org.apache.commons.logging.Log;
24+
import org.apache.commons.logging.LogFactory;
25+
26+
/**
27+
* A simple implementation of {@link RequestRejectedHandler} that sends an error with configurable status code.
28+
*
29+
* @author Leonard Brünings
30+
* @since 5.2
31+
*/
32+
public class HttpStatusRequestRejectedHandler implements RequestRejectedHandler {
33+
private static final Log logger = LogFactory.getLog(HttpStatusRequestRejectedHandler.class);
34+
35+
private final int httpError;
36+
37+
/**
38+
* Constructs an instance which uses {@code 400} as response code.
39+
*/
40+
public HttpStatusRequestRejectedHandler() {
41+
httpError = HttpServletResponse.SC_BAD_REQUEST;
42+
}
43+
44+
/**
45+
* Constructs an instance which uses a configurable http code as response.
46+
* @param httpError http status code to use
47+
*/
48+
public HttpStatusRequestRejectedHandler(int httpError) {
49+
this.httpError = httpError;
50+
}
51+
52+
@Override
53+
public void handle(HttpServletRequest request, HttpServletResponse response,
54+
RequestRejectedException requestRejectedException) throws IOException {
55+
if (logger.isDebugEnabled()) {
56+
logger.debug("Rejecting request due to: " + requestRejectedException.getMessage(),
57+
requestRejectedException);
58+
}
59+
response.sendError(httpError);
60+
}
61+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2002-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.security.web.firewall;
17+
18+
import java.io.IOException;
19+
20+
import javax.servlet.ServletException;
21+
import javax.servlet.http.HttpServletRequest;
22+
import javax.servlet.http.HttpServletResponse;
23+
24+
/**
25+
* Used by {@link org.springframework.security.web.FilterChainProxy} to handle an
26+
* <code>RequestRejectedException</code>.
27+
*
28+
* @author Leonard Brünings
29+
* @since 5.2
30+
*/
31+
public interface RequestRejectedHandler {
32+
// ~ Methods
33+
// ========================================================================================================
34+
35+
/**
36+
* Handles an request rejected failure.
37+
*
38+
* @param request that resulted in an <code>RequestRejectedException</code>
39+
* @param response so that the user agent can be advised of the failure
40+
* @param requestRejectedException that caused the invocation
41+
*
42+
* @throws IOException in the event of an IOException
43+
* @throws ServletException in the event of a ServletException
44+
*/
45+
void handle(HttpServletRequest request, HttpServletResponse response,
46+
RequestRejectedException requestRejectedException) throws IOException,
47+
ServletException;
48+
}

web/src/test/java/org/springframework/security/web/FilterChainProxyTests.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import org.springframework.security.core.context.SecurityContextHolder;
2929
import org.springframework.security.web.firewall.FirewalledRequest;
3030
import org.springframework.security.web.firewall.HttpFirewall;
31+
import org.springframework.security.web.firewall.RequestRejectedException;
32+
import org.springframework.security.web.firewall.RequestRejectedHandler;
3133
import org.springframework.security.web.util.matcher.RequestMatcher;
3234

3335
import javax.servlet.Filter;
@@ -243,4 +245,21 @@ public void doFilterClearsSecurityContextHolderOnceOnForwards() throws Exception
243245
any(HttpServletResponse.class));
244246
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
245247
}
248+
249+
@Test(expected = IllegalArgumentException.class)
250+
public void setRequestRejectedHandlerDoesNotAcceptNull() {
251+
fcp.setRequestRejectedHandler(null);
252+
}
253+
254+
@Test
255+
public void requestRejectedHandlerIsCalledIfFirewallThrowsRequestRejectedException() throws Exception {
256+
HttpFirewall fw = mock(HttpFirewall.class);
257+
RequestRejectedHandler rjh = mock(RequestRejectedHandler.class);
258+
fcp.setFirewall(fw);
259+
fcp.setRequestRejectedHandler(rjh);
260+
RequestRejectedException requestRejectedException = new RequestRejectedException("Contains illegal chars");
261+
when(fw.getFirewalledRequest(request)).thenThrow(requestRejectedException);
262+
fcp.doFilter(request, response, chain);
263+
verify(rjh).handle(eq(request), eq(response), eq((requestRejectedException)));
264+
}
246265
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2002-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.security.web.firewall;
17+
18+
19+
import static org.mockito.Mockito.mock;
20+
21+
import javax.servlet.http.HttpServletRequest;
22+
import javax.servlet.http.HttpServletResponse;
23+
24+
import org.hamcrest.CoreMatchers;
25+
import org.junit.Assert;
26+
import org.junit.Test;
27+
28+
public class DefaultRequestRejectedHandlerTest {
29+
30+
@Test
31+
public void defaultRequestRejectedHandlerRethrowsTheException() throws Exception {
32+
// given:
33+
RequestRejectedException requestRejectedException = new RequestRejectedException("rejected");
34+
DefaultRequestRejectedHandler sut = new DefaultRequestRejectedHandler();
35+
36+
//when:
37+
try {
38+
sut.handle(mock(HttpServletRequest.class), mock(HttpServletResponse.class), requestRejectedException);
39+
} catch (RequestRejectedException exception) {
40+
//then:
41+
Assert.assertThat(exception.getMessage(), CoreMatchers.is("rejected"));
42+
return;
43+
}
44+
Assert.fail("Exception was not rethrown");
45+
}
46+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright 2002-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.security.web.firewall;
17+
18+
19+
import static org.mockito.Mockito.mock;
20+
import static org.mockito.Mockito.verify;
21+
22+
import javax.servlet.http.HttpServletRequest;
23+
import javax.servlet.http.HttpServletResponse;
24+
25+
import org.junit.Test;
26+
27+
public class HttpStatusRequestRejectedHandlerTest {
28+
29+
@Test
30+
public void httpStatusRequestRejectedHandlerUsesStatus400byDefault() throws Exception {
31+
//given:
32+
HttpStatusRequestRejectedHandler sut = new HttpStatusRequestRejectedHandler();
33+
HttpServletResponse response = mock(HttpServletResponse.class);
34+
35+
//when:
36+
sut.handle(mock(HttpServletRequest.class), response, mock(RequestRejectedException.class));
37+
38+
// then:
39+
verify(response).sendError(400);
40+
}
41+
42+
@Test
43+
public void httpStatusRequestRejectedHandlerCanBeConfiguredToUseStatus() throws Exception {
44+
httpStatusRequestRejectedHandlerCanBeConfiguredToUseStatusHelper(400);
45+
httpStatusRequestRejectedHandlerCanBeConfiguredToUseStatusHelper(403);
46+
httpStatusRequestRejectedHandlerCanBeConfiguredToUseStatusHelper(500);
47+
}
48+
49+
private void httpStatusRequestRejectedHandlerCanBeConfiguredToUseStatusHelper(int status) throws Exception {
50+
51+
//given:
52+
HttpStatusRequestRejectedHandler sut = new HttpStatusRequestRejectedHandler(status);
53+
HttpServletResponse response = mock(HttpServletResponse.class);
54+
55+
//when:
56+
sut.handle(mock(HttpServletRequest.class), response, mock(RequestRejectedException.class));
57+
58+
// then:
59+
verify(response).sendError(status);
60+
}
61+
}

0 commit comments

Comments
 (0)