Skip to content

Commit 47fee17

Browse files
author
Hatef Palizgar
committed
add tests to ensure correct order is picked up by context
1 parent 8dbd90e commit 47fee17

File tree

2 files changed

+93
-1
lines changed

2 files changed

+93
-1
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ dependencies {
108108
optional("redis.clients:jedis")
109109

110110
testImplementation(project(":spring-boot-project:spring-boot-test"))
111+
testImplementation(project(":spring-boot-project:spring-boot-devtools"))
111112
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
112113
testImplementation("io.projectreactor:reactor-test")
113114
testImplementation("io.r2dbc:r2dbc-h2")

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/ManagementWebSecurityAutoConfigurationTests.java

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,41 @@
1717
package org.springframework.boot.actuate.autoconfigure.security.servlet;
1818

1919
import java.io.IOException;
20+
import java.util.Arrays;
21+
import java.util.Comparator;
22+
import java.util.List;
23+
import java.util.Map;
24+
import java.util.Optional;
25+
import java.util.concurrent.ConcurrentHashMap;
26+
import java.util.stream.Collectors;
2027

28+
import org.jetbrains.annotations.NotNull;
2129
import org.junit.jupiter.api.Test;
2230

31+
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
32+
import org.springframework.beans.factory.config.BeanDefinitionHolder;
2333
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
2434
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
2535
import org.springframework.boot.actuate.autoconfigure.env.EnvironmentEndpointAutoConfiguration;
2636
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
2737
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
2838
import org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfiguration;
2939
import org.springframework.boot.autoconfigure.AutoConfigurations;
40+
import org.springframework.boot.autoconfigure.security.SecurityProperties;
3041
import org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration;
3142
import org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration;
3243
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
3344
import org.springframework.boot.test.context.FilteredClassLoader;
3445
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
3546
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
47+
import org.springframework.context.ConfigurableApplicationContext;
3648
import org.springframework.context.annotation.Bean;
3749
import org.springframework.context.annotation.Configuration;
50+
import org.springframework.core.Ordered;
51+
import org.springframework.core.ResolvableType;
52+
import org.springframework.core.annotation.AnnotationAttributes;
53+
import org.springframework.core.annotation.AnnotationUtils;
54+
import org.springframework.core.annotation.Order;
3855
import org.springframework.http.HttpStatus;
3956
import org.springframework.mock.web.MockFilterChain;
4057
import org.springframework.mock.web.MockHttpServletRequest;
@@ -45,6 +62,7 @@
4562
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
4663
import org.springframework.security.web.FilterChainProxy;
4764
import org.springframework.security.web.SecurityFilterChain;
65+
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
4866
import org.springframework.web.context.WebApplicationContext;
4967

5068
import static org.assertj.core.api.Assertions.assertThat;
@@ -113,7 +131,7 @@ void backOffIfCustomSecurityIsAdded() {
113131
@Test
114132
void backsOffIfSecurityFilterChainBeanIsPresent() {
115133
this.contextRunner.withUserConfiguration(TestSecurityFilterChainConfig.class).run((context) -> {
116-
assertThat(context.getBeansOfType(SecurityFilterChain.class).size()).isEqualTo(1);
134+
assertThat(context.getBeansOfType(SecurityFilterChain.class)).isNotEmpty();
117135
assertThat(context.containsBean("testSecurityFilterChain")).isTrue();
118136
});
119137
}
@@ -138,6 +156,72 @@ void backOffIfSaml2RelyingPartyAutoConfigurationPresent() {
138156
.doesNotHaveBean(MANAGEMENT_SECURITY_FILTER_CHAIN_BEAN));
139157
}
140158

159+
@Test
160+
void backOffIfRemoteDevToolsSecurityFilterChainIsPresent() {
161+
this.contextRunner.withUserConfiguration(TestSecurityFilterChainConfig.class).run((context) -> {
162+
List<String> beanNames = getOrderedBeanNames(context);
163+
164+
assertThat(beanNames).containsExactly("testRemoteDevToolsSecurityFilterChain", "testSecurityFilterChain");
165+
assertThat(context.getBeansOfType(SecurityFilterChain.class).size()).isEqualTo(2);
166+
assertThat(context).doesNotHaveBean(ManagementWebSecurityAutoConfiguration.class);
167+
assertThat(context.containsBean("testRemoteDevToolsSecurityFilterChain")).isTrue();
168+
});
169+
}
170+
171+
@NotNull
172+
private List<String> getOrderedBeanNames(AssertableWebApplicationContext context) {
173+
return Arrays.stream(context.getBeanNamesForType(SecurityFilterChain.class))
174+
.map(beanName -> Optional.of(context).map(ConfigurableApplicationContext::getBeanFactory)
175+
.map(beanFactory -> beanFactory.getBeanDefinition(beanName))
176+
.map(beanDefinition -> new BeanDefinitionHolder(beanDefinition, beanName)).orElse(null))
177+
.sorted(OrderAnnotatedBeanDefinitionComparator.INSTANCE).map(BeanDefinitionHolder::getBeanName)
178+
.collect(Collectors.toList());
179+
}
180+
181+
static class OrderAnnotatedBeanDefinitionComparator implements Comparator<BeanDefinitionHolder> {
182+
183+
static final OrderAnnotatedBeanDefinitionComparator INSTANCE = new OrderAnnotatedBeanDefinitionComparator();
184+
185+
private final Map<String, Integer> beanNameToOrder = new ConcurrentHashMap<>();
186+
187+
@Override
188+
public int compare(BeanDefinitionHolder beanOne, BeanDefinitionHolder beanTwo) {
189+
return getOrder(beanOne).compareTo(getOrder(beanTwo));
190+
}
191+
192+
private Integer getOrder(BeanDefinitionHolder bean) {
193+
return this.beanNameToOrder.computeIfAbsent(bean.getBeanName(),
194+
beanName -> Optional.of(bean).map(BeanDefinitionHolder::getBeanDefinition)
195+
.filter(AnnotatedBeanDefinition.class::isInstance).map(AnnotatedBeanDefinition.class::cast)
196+
.map(this::getOrderAnnotationAttributesFromFactoryMethod).map(this::getOrder)
197+
.orElse(Ordered.LOWEST_PRECEDENCE));
198+
}
199+
200+
private Integer getOrder(AnnotationAttributes annotationAttributes) {
201+
return Optional.ofNullable(annotationAttributes)
202+
.map(it -> it.getOrDefault("value", Ordered.LOWEST_PRECEDENCE)).map(Integer.class::cast)
203+
.orElse(Ordered.LOWEST_PRECEDENCE);
204+
}
205+
206+
private AnnotationAttributes getOrderAnnotationAttributesFromFactoryMethod(
207+
AnnotatedBeanDefinition beanDefinition) {
208+
return Optional.of(beanDefinition).map(AnnotatedBeanDefinition::getFactoryMethodMetadata)
209+
.filter(methodMetadata -> methodMetadata.isAnnotated(Order.class.getName()))
210+
.map(methodMetadata -> methodMetadata.getAnnotationAttributes(Order.class.getName()))
211+
.map(AnnotationAttributes::fromMap)
212+
.orElseGet(() -> getOrderAnnotationAttributesFromBeanClass(beanDefinition));
213+
}
214+
215+
private AnnotationAttributes getOrderAnnotationAttributesFromBeanClass(AnnotatedBeanDefinition beanDefinition) {
216+
return Optional.of(beanDefinition).map(AnnotatedBeanDefinition::getResolvableType)
217+
.map(ResolvableType::resolve).filter(beanType -> beanType.isAnnotationPresent(Order.class))
218+
.map(beanType -> beanType.getAnnotation(Order.class)).map(AnnotationUtils::getAnnotationAttributes)
219+
.map(AnnotationAttributes::fromMap).orElse(null);
220+
221+
}
222+
223+
}
224+
141225
private HttpStatus getResponseStatus(AssertableWebApplicationContext context, String path)
142226
throws IOException, javax.servlet.ServletException {
143227
FilterChainProxy filterChainProxy = context.getBean(FilterChainProxy.class);
@@ -175,6 +259,13 @@ SecurityFilterChain testSecurityFilterChain(HttpSecurity http) throws Exception
175259
.build();
176260
}
177261

262+
@Bean
263+
@Order(SecurityProperties.BASIC_AUTH_ORDER - 1)
264+
SecurityFilterChain testRemoteDevToolsSecurityFilterChain(HttpSecurity http) throws Exception {
265+
return http.requestMatcher(new AntPathRequestMatcher("/**")).authorizeRequests().anyRequest().anonymous()
266+
.and().csrf().disable().build();
267+
}
268+
178269
}
179270

180271
}

0 commit comments

Comments
 (0)