Skip to content

Commit f884e34

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

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,28 @@ 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+
141181
private HttpStatus getResponseStatus(AssertableWebApplicationContext context, String path)
142182
throws IOException, javax.servlet.ServletException {
143183
FilterChainProxy filterChainProxy = context.getBean(FilterChainProxy.class);
@@ -175,6 +215,57 @@ SecurityFilterChain testSecurityFilterChain(HttpSecurity http) throws Exception
175215
.build();
176216
}
177217

218+
@Bean
219+
@Order(SecurityProperties.BASIC_AUTH_ORDER - 1)
220+
SecurityFilterChain testRemoteDevToolsSecurityFilterChain(HttpSecurity http) throws Exception {
221+
return http.requestMatcher(new AntPathRequestMatcher("/**")).authorizeRequests().anyRequest().anonymous()
222+
.and().csrf().disable().build();
223+
}
224+
225+
}
226+
227+
static class OrderAnnotatedBeanDefinitionComparator implements Comparator<BeanDefinitionHolder> {
228+
229+
static final OrderAnnotatedBeanDefinitionComparator INSTANCE = new OrderAnnotatedBeanDefinitionComparator();
230+
231+
private final Map<String, Integer> beanNameToOrder = new ConcurrentHashMap<>();
232+
233+
@Override
234+
public int compare(BeanDefinitionHolder beanOne, BeanDefinitionHolder beanTwo) {
235+
return getOrder(beanOne).compareTo(getOrder(beanTwo));
236+
}
237+
238+
private Integer getOrder(BeanDefinitionHolder bean) {
239+
return this.beanNameToOrder.computeIfAbsent(bean.getBeanName(),
240+
(beanName) -> Optional.of(bean).map(BeanDefinitionHolder::getBeanDefinition)
241+
.filter(AnnotatedBeanDefinition.class::isInstance).map(AnnotatedBeanDefinition.class::cast)
242+
.map(this::getOrderAnnotationAttributesFromFactoryMethod).map(this::getOrder)
243+
.orElse(Ordered.LOWEST_PRECEDENCE));
244+
}
245+
246+
private Integer getOrder(AnnotationAttributes annotationAttributes) {
247+
return Optional.ofNullable(annotationAttributes)
248+
.map((it) -> it.getOrDefault("value", Ordered.LOWEST_PRECEDENCE)).map(Integer.class::cast)
249+
.orElse(Ordered.LOWEST_PRECEDENCE);
250+
}
251+
252+
private AnnotationAttributes getOrderAnnotationAttributesFromFactoryMethod(
253+
AnnotatedBeanDefinition beanDefinition) {
254+
return Optional.of(beanDefinition).map(AnnotatedBeanDefinition::getFactoryMethodMetadata)
255+
.filter((methodMetadata) -> methodMetadata.isAnnotated(Order.class.getName()))
256+
.map((methodMetadata) -> methodMetadata.getAnnotationAttributes(Order.class.getName()))
257+
.map(AnnotationAttributes::fromMap)
258+
.orElseGet(() -> getOrderAnnotationAttributesFromBeanClass(beanDefinition));
259+
}
260+
261+
private AnnotationAttributes getOrderAnnotationAttributesFromBeanClass(AnnotatedBeanDefinition beanDefinition) {
262+
return Optional.of(beanDefinition).map(AnnotatedBeanDefinition::getResolvableType)
263+
.map(ResolvableType::resolve).filter((beanType) -> beanType.isAnnotationPresent(Order.class))
264+
.map((beanType) -> beanType.getAnnotation(Order.class))
265+
.map(AnnotationUtils::getAnnotationAttributes).map(AnnotationAttributes::fromMap).orElse(null);
266+
267+
}
268+
178269
}
179270

180271
}

0 commit comments

Comments
 (0)