Skip to content

Commit c81f19e

Browse files
Add support checking same security matchers
Closes gh-15982
1 parent ff7dbb4 commit c81f19e

File tree

4 files changed

+65
-12
lines changed

4 files changed

+65
-12
lines changed

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

+2
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
4545
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
4646
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
47+
import org.springframework.security.config.http.DefaultFilterChainValidator;
4748
import org.springframework.security.core.context.SecurityContext;
4849
import org.springframework.security.web.DefaultSecurityFilterChain;
4950
import org.springframework.security.web.FilterChainProxy;
@@ -338,6 +339,7 @@ else if (!this.observationRegistry.isNoop()) {
338339
filterChainProxy.setRequestRejectedHandler(requestRejectedHandler);
339340
}
340341
filterChainProxy.setFilterChainDecorator(getFilterChainDecorator());
342+
filterChainProxy.setFilterChainValidator(new DefaultFilterChainValidator());
341343
filterChainProxy.afterPropertiesSet();
342344

343345
Filter result = filterChainProxy;

config/src/main/java/org/springframework/security/config/http/DefaultFilterChainValidator.java

+13-10
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,12 @@ private void checkPathOrder(List<SecurityFilterChain> filterChains) {
7575
// Check that the universal pattern is listed at the end, if at all
7676
Iterator<SecurityFilterChain> chains = filterChains.iterator();
7777
while (chains.hasNext()) {
78-
RequestMatcher matcher = ((DefaultSecurityFilterChain) chains.next()).getRequestMatcher();
79-
if (AnyRequestMatcher.INSTANCE.equals(matcher) && chains.hasNext()) {
80-
throw new IllegalArgumentException("A universal match pattern ('/**') is defined "
81-
+ " before other patterns in the filter chain, causing them to be ignored. Please check the "
82-
+ "ordering in your <security:http> namespace or FilterChainProxy bean configuration");
78+
if (chains.next() instanceof DefaultSecurityFilterChain securityFilterChain) {
79+
if (AnyRequestMatcher.INSTANCE.equals(securityFilterChain.getRequestMatcher()) && chains.hasNext()) {
80+
throw new IllegalArgumentException("A universal match pattern ('/**') is defined "
81+
+ " before other patterns in the filter chain, causing them to be ignored. Please check the "
82+
+ "ordering in your <security:http> namespace or FilterChainProxy bean configuration");
83+
}
8384
}
8485
}
8586
}
@@ -88,11 +89,13 @@ private void checkForDuplicateMatchers(List<SecurityFilterChain> chains) {
8889
while (chains.size() > 1) {
8990
DefaultSecurityFilterChain chain = (DefaultSecurityFilterChain) chains.remove(0);
9091
for (SecurityFilterChain test : chains) {
91-
if (chain.getRequestMatcher().equals(((DefaultSecurityFilterChain) test).getRequestMatcher())) {
92-
throw new IllegalArgumentException("The FilterChainProxy contains two filter chains using the"
93-
+ " matcher " + chain.getRequestMatcher() + ". If you are using multiple <http> namespace "
94-
+ "elements, you must use a 'pattern' attribute to define the request patterns to which they apply.");
95-
}
92+
if (test instanceof DefaultSecurityFilterChain securityFilterChain)
93+
if (chain.getRequestMatcher().equals(securityFilterChain.getRequestMatcher())) {
94+
throw new IllegalArgumentException("The FilterChainProxy contains two filter chains using the"
95+
+ " matcher " + chain.getRequestMatcher()
96+
+ ". If you are using multiple <http> namespace "
97+
+ "elements, you must use a 'pattern' attribute to define the request patterns to which they apply.");
98+
}
9699
}
97100
}
98101
}

config/src/test/java/org/springframework/security/config/annotation/web/builders/WebSecurityTests.java

+31-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -27,9 +27,11 @@
2727
import org.junit.jupiter.api.BeforeEach;
2828
import org.junit.jupiter.api.Test;
2929

30+
import org.springframework.beans.factory.BeanCreationException;
3031
import org.springframework.beans.factory.annotation.Autowired;
3132
import org.springframework.context.annotation.Bean;
3233
import org.springframework.context.annotation.Configuration;
34+
import org.springframework.core.annotation.Order;
3335
import org.springframework.http.HttpStatus;
3436
import org.springframework.mock.web.MockFilterChain;
3537
import org.springframework.mock.web.MockHttpServletRequest;
@@ -52,6 +54,7 @@
5254
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
5355

5456
import static org.assertj.core.api.Assertions.assertThat;
57+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
5558

5659
/**
5760
* @author Rob Winch
@@ -156,6 +159,12 @@ public void ignoringMvcMatcherServletPath() throws Exception {
156159
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
157160
}
158161

162+
@Test
163+
public void configureWhenSameSecurityMatchersConfiguredThenThrowsBeanCreationException() {
164+
assertThatExceptionOfType(BeanCreationException.class)
165+
.isThrownBy(() -> loadConfig(MultipleSecurityMatchersConfig.class));
166+
}
167+
159168
public void loadConfig(Class<?>... configs) {
160169
this.context = new AnnotationConfigWebApplicationContext();
161170
this.context.register(configs);
@@ -169,6 +178,27 @@ static class DefaultConfig {
169178

170179
}
171180

181+
@Configuration
182+
@EnableWebMvc
183+
@EnableWebSecurity
184+
static class MultipleSecurityMatchersConfig {
185+
186+
@Bean
187+
@Order(0)
188+
SecurityFilterChain app(HttpSecurity http) throws Exception {
189+
http.securityMatcher("/app/**").authorizeHttpRequests((auth) -> auth.anyRequest().authenticated());
190+
return http.build();
191+
}
192+
193+
@Bean
194+
@Order(1)
195+
SecurityFilterChain api(HttpSecurity http) throws Exception {
196+
http.securityMatcher("/app/**").authorizeHttpRequests((auth) -> auth.anyRequest().authenticated());
197+
return http.build();
198+
}
199+
200+
}
201+
172202
@EnableWebSecurity
173203
@Configuration
174204
@EnableWebMvc

web/src/main/java/org/springframework/security/web/util/matcher/OrRequestMatcher.java

+19-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@
1818

1919
import java.util.Arrays;
2020
import java.util.List;
21+
import java.util.Objects;
2122

2223
import jakarta.servlet.http.HttpServletRequest;
2324

@@ -81,6 +82,23 @@ public MatchResult matcher(HttpServletRequest request) {
8182
return MatchResult.notMatch();
8283
}
8384

85+
@Override
86+
public boolean equals(Object o) {
87+
if (this == o) {
88+
return true;
89+
}
90+
if (o == null || getClass() != o.getClass()) {
91+
return false;
92+
}
93+
OrRequestMatcher that = (OrRequestMatcher) o;
94+
return Objects.equals(this.requestMatchers, that.requestMatchers);
95+
}
96+
97+
@Override
98+
public int hashCode() {
99+
return Objects.hash(this.requestMatchers);
100+
}
101+
84102
@Override
85103
public String toString() {
86104
return "Or " + this.requestMatchers;

0 commit comments

Comments
 (0)