Skip to content

Commit 0e5bfa4

Browse files
Add Support Same Request Matchers Checking
Closes gh-15982
1 parent 8061318 commit 0e5bfa4

File tree

3 files changed

+115
-2
lines changed

3 files changed

+115
-2
lines changed

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

+26-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.springframework.security.web.DefaultSecurityFilterChain;
2222
import org.springframework.security.web.FilterChainProxy;
2323
import org.springframework.security.web.SecurityFilterChain;
24+
import org.springframework.security.web.UnreachableFilterChainException;
2425
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
2526

2627
/**
@@ -33,13 +34,18 @@ final class WebSecurityFilterChainValidator implements FilterChainProxy.FilterCh
3334
@Override
3435
public void validate(FilterChainProxy filterChainProxy) {
3536
List<SecurityFilterChain> chains = filterChainProxy.getFilterChains();
37+
checkForAnyRequestRequestMatcher(chains);
38+
checkForDuplicateMatchers(chains);
39+
}
40+
41+
private void checkForAnyRequestRequestMatcher(List<SecurityFilterChain> chains) {
3642
DefaultSecurityFilterChain anyRequestFilterChain = null;
3743
for (SecurityFilterChain chain : chains) {
3844
if (anyRequestFilterChain != null) {
3945
String message = "A filter chain that matches any request [" + anyRequestFilterChain
4046
+ "] has already been configured, which means that this filter chain [" + chain
4147
+ "] will never get invoked. Please use `HttpSecurity#securityMatcher` to ensure that there is only one filter chain configured for 'any request' and that the 'any request' filter chain is published last.";
42-
throw new IllegalArgumentException(message);
48+
throw new UnreachableFilterChainException(message, anyRequestFilterChain, chain);
4349
}
4450
if (chain instanceof DefaultSecurityFilterChain defaultChain) {
4551
if (defaultChain.getRequestMatcher() instanceof AnyRequestMatcher) {
@@ -49,4 +55,23 @@ public void validate(FilterChainProxy filterChainProxy) {
4955
}
5056
}
5157

58+
private void checkForDuplicateMatchers(List<SecurityFilterChain> chains) {
59+
DefaultSecurityFilterChain filterChain = null;
60+
for (SecurityFilterChain chain : chains) {
61+
if (filterChain != null) {
62+
if (chain instanceof DefaultSecurityFilterChain defaultChain) {
63+
if (defaultChain.getRequestMatcher().equals(filterChain.getRequestMatcher())) {
64+
throw new UnreachableFilterChainException(
65+
"The FilterChainProxy contains two filter chains using the" + " matcher "
66+
+ defaultChain.getRequestMatcher(),
67+
filterChain, defaultChain);
68+
}
69+
}
70+
}
71+
if (chain instanceof DefaultSecurityFilterChain defaultChain) {
72+
filterChain = defaultChain;
73+
}
74+
}
75+
}
76+
5277
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright 2002-2024 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+
17+
package org.springframework.security.config.annotation.web.builders;
18+
19+
import java.util.ArrayList;
20+
import java.util.List;
21+
22+
import org.junit.jupiter.api.Test;
23+
import org.junit.jupiter.api.extension.ExtendWith;
24+
import org.mockito.Mock;
25+
import org.mockito.junit.jupiter.MockitoExtension;
26+
27+
import org.springframework.security.web.DefaultSecurityFilterChain;
28+
import org.springframework.security.web.FilterChainProxy;
29+
import org.springframework.security.web.SecurityFilterChain;
30+
import org.springframework.security.web.UnreachableFilterChainException;
31+
import org.springframework.security.web.access.ExceptionTranslationFilter;
32+
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
33+
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
34+
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
35+
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
36+
37+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
38+
39+
/**
40+
* Tests for {@link WebSecurityFilterChainValidator}
41+
*
42+
* @author Max Batischev
43+
*/
44+
@ExtendWith(MockitoExtension.class)
45+
public class WebSecurityFilterChainValidatorTests {
46+
47+
private final WebSecurityFilterChainValidator validator = new WebSecurityFilterChainValidator();
48+
49+
@Mock
50+
private AnonymousAuthenticationFilter authenticationFilter;
51+
52+
@Mock
53+
private ExceptionTranslationFilter exceptionTranslationFilter;
54+
55+
@Mock
56+
private FilterSecurityInterceptor authorizationInterceptor;
57+
58+
@Test
59+
void validateWhenAnyRequestMatcherIsPresentThenUnreachableFilterChainException() {
60+
SecurityFilterChain chain1 = new DefaultSecurityFilterChain(AntPathRequestMatcher.antMatcher("/api"),
61+
this.authenticationFilter, this.exceptionTranslationFilter, this.authorizationInterceptor);
62+
SecurityFilterChain chain2 = new DefaultSecurityFilterChain(AnyRequestMatcher.INSTANCE,
63+
this.authenticationFilter, this.exceptionTranslationFilter, this.authorizationInterceptor);
64+
List<SecurityFilterChain> chains = new ArrayList<>();
65+
chains.add(chain2);
66+
chains.add(chain1);
67+
FilterChainProxy proxy = new FilterChainProxy(chains);
68+
69+
assertThatExceptionOfType(UnreachableFilterChainException.class)
70+
.isThrownBy(() -> this.validator.validate(proxy));
71+
}
72+
73+
@Test
74+
void validateWhenSameRequestMatchersArePresentThenUnreachableFilterChainException() {
75+
SecurityFilterChain chain1 = new DefaultSecurityFilterChain(AntPathRequestMatcher.antMatcher("/api"),
76+
this.authenticationFilter, this.exceptionTranslationFilter, this.authorizationInterceptor);
77+
SecurityFilterChain chain2 = new DefaultSecurityFilterChain(AntPathRequestMatcher.antMatcher("/api"),
78+
this.authenticationFilter, this.exceptionTranslationFilter, this.authorizationInterceptor);
79+
List<SecurityFilterChain> chains = new ArrayList<>();
80+
chains.add(chain2);
81+
chains.add(chain1);
82+
FilterChainProxy proxy = new FilterChainProxy(chains);
83+
84+
assertThatExceptionOfType(UnreachableFilterChainException.class)
85+
.isThrownBy(() -> this.validator.validate(proxy));
86+
}
87+
88+
}

config/src/test/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurationTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ public void loadConfigWhenTwoSecurityFilterChainsPresentAndSecondWithAnyRequestT
323323
assertThatExceptionOfType(BeanCreationException.class)
324324
.isThrownBy(() -> this.spring.register(MultipleAnyRequestSecurityFilterChainConfig.class).autowire())
325325
.havingRootCause()
326-
.isExactlyInstanceOf(IllegalArgumentException.class);
326+
.isInstanceOf(IllegalArgumentException.class);
327327
}
328328

329329
private void assertAnotherUserPermission(WebInvocationPrivilegeEvaluator privilegeEvaluator) {

0 commit comments

Comments
 (0)