Skip to content

Commit 4967fe5

Browse files
Add Support Same Request Matchers Checking
Closes gh-15982
1 parent b9e1481 commit 4967fe5

File tree

2 files changed

+115
-1
lines changed

2 files changed

+115
-1
lines changed

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

+27-1
Original file line numberDiff line numberDiff line change
@@ -21,25 +21,33 @@
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
/**
2728
* A filter chain validator for filter chains built by {@link WebSecurity}
2829
*
30+
* @author Josh Cummings
31+
* @author Max Batischev
2932
* @since 6.5
3033
*/
3134
final class WebSecurityFilterChainValidator implements FilterChainProxy.FilterChainValidator {
3235

3336
@Override
3437
public void validate(FilterChainProxy filterChainProxy) {
3538
List<SecurityFilterChain> chains = filterChainProxy.getFilterChains();
39+
checkForAnyRequestRequestMatcher(chains);
40+
checkForDuplicateMatchers(chains);
41+
}
42+
43+
private void checkForAnyRequestRequestMatcher(List<SecurityFilterChain> chains) {
3644
DefaultSecurityFilterChain anyRequestFilterChain = null;
3745
for (SecurityFilterChain chain : chains) {
3846
if (anyRequestFilterChain != null) {
3947
String message = "A filter chain that matches any request [" + anyRequestFilterChain
4048
+ "] has already been configured, which means that this filter chain [" + chain
4149
+ "] 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);
50+
throw new UnreachableFilterChainException(message);
4351
}
4452
if (chain instanceof DefaultSecurityFilterChain defaultChain) {
4553
if (defaultChain.getRequestMatcher() instanceof AnyRequestMatcher) {
@@ -49,4 +57,22 @@ public void validate(FilterChainProxy filterChainProxy) {
4957
}
5058
}
5159

60+
private void checkForDuplicateMatchers(List<SecurityFilterChain> chains) {
61+
DefaultSecurityFilterChain filterChain = null;
62+
for (SecurityFilterChain chain : chains) {
63+
if (filterChain != null) {
64+
if (chain instanceof DefaultSecurityFilterChain defaultChain) {
65+
if (defaultChain.getRequestMatcher().equals(filterChain.getRequestMatcher())) {
66+
throw new UnreachableFilterChainException(
67+
"The FilterChainProxy contains two filter chains using the" + " matcher "
68+
+ defaultChain.getRequestMatcher());
69+
}
70+
}
71+
}
72+
if (chain instanceof DefaultSecurityFilterChain defaultChain) {
73+
filterChain = defaultChain;
74+
}
75+
}
76+
}
77+
5278
}
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+
}

0 commit comments

Comments
 (0)