1717package org .springframework .boot .actuate .autoconfigure .security .servlet ;
1818
1919import 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 ;
2129import org .junit .jupiter .api .Test ;
2230
31+ import org .springframework .beans .factory .annotation .AnnotatedBeanDefinition ;
32+ import org .springframework .beans .factory .config .BeanDefinitionHolder ;
2333import org .springframework .boot .actuate .autoconfigure .endpoint .EndpointAutoConfiguration ;
2434import org .springframework .boot .actuate .autoconfigure .endpoint .web .WebEndpointAutoConfiguration ;
2535import org .springframework .boot .actuate .autoconfigure .env .EnvironmentEndpointAutoConfiguration ;
2636import org .springframework .boot .actuate .autoconfigure .health .HealthContributorAutoConfiguration ;
2737import org .springframework .boot .actuate .autoconfigure .health .HealthEndpointAutoConfiguration ;
2838import org .springframework .boot .actuate .autoconfigure .info .InfoEndpointAutoConfiguration ;
2939import org .springframework .boot .autoconfigure .AutoConfigurations ;
40+ import org .springframework .boot .autoconfigure .security .SecurityProperties ;
3041import org .springframework .boot .autoconfigure .security .oauth2 .resource .servlet .OAuth2ResourceServerAutoConfiguration ;
3142import org .springframework .boot .autoconfigure .security .saml2 .Saml2RelyingPartyAutoConfiguration ;
3243import org .springframework .boot .autoconfigure .security .servlet .SecurityAutoConfiguration ;
3344import org .springframework .boot .test .context .FilteredClassLoader ;
3445import org .springframework .boot .test .context .assertj .AssertableWebApplicationContext ;
3546import org .springframework .boot .test .context .runner .WebApplicationContextRunner ;
47+ import org .springframework .context .ConfigurableApplicationContext ;
3648import org .springframework .context .annotation .Bean ;
3749import 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 ;
3855import org .springframework .http .HttpStatus ;
3956import org .springframework .mock .web .MockFilterChain ;
4057import org .springframework .mock .web .MockHttpServletRequest ;
4562import org .springframework .security .config .annotation .web .configuration .WebSecurityConfigurerAdapter ;
4663import org .springframework .security .web .FilterChainProxy ;
4764import org .springframework .security .web .SecurityFilterChain ;
65+ import org .springframework .security .web .util .matcher .AntPathRequestMatcher ;
4866import org .springframework .web .context .WebApplicationContext ;
4967
5068import static org .assertj .core .api .Assertions .assertThat ;
@@ -121,7 +139,7 @@ void backOffIfCustomSecurityIsAdded() {
121139 @ Test
122140 void backsOffIfSecurityFilterChainBeanIsPresent () {
123141 this .contextRunner .withUserConfiguration (TestSecurityFilterChainConfig .class ).run ((context ) -> {
124- assertThat (context .getBeansOfType (SecurityFilterChain .class ). size ()). isEqualTo ( 1 );
142+ assertThat (context .getBeansOfType (SecurityFilterChain .class )). isNotEmpty ( );
125143 assertThat (context .containsBean ("testSecurityFilterChain" )).isTrue ();
126144 });
127145 }
@@ -146,6 +164,28 @@ void backOffIfSaml2RelyingPartyAutoConfigurationPresent() {
146164 .doesNotHaveBean (MANAGEMENT_SECURITY_FILTER_CHAIN_BEAN ));
147165 }
148166
167+ @ Test
168+ void backOffIfRemoteDevToolsSecurityFilterChainIsPresent () {
169+ this .contextRunner .withUserConfiguration (TestSecurityFilterChainConfig .class ).run ((context ) -> {
170+ List <String > beanNames = getOrderedBeanNames (context );
171+
172+ assertThat (beanNames ).containsExactly ("testRemoteDevToolsSecurityFilterChain" , "testSecurityFilterChain" );
173+ assertThat (context .getBeansOfType (SecurityFilterChain .class ).size ()).isEqualTo (2 );
174+ assertThat (context ).doesNotHaveBean (ManagementWebSecurityAutoConfiguration .class );
175+ assertThat (context .containsBean ("testRemoteDevToolsSecurityFilterChain" )).isTrue ();
176+ });
177+ }
178+
179+ @ NotNull
180+ private List <String > getOrderedBeanNames (AssertableWebApplicationContext context ) {
181+ return Arrays .stream (context .getBeanNamesForType (SecurityFilterChain .class ))
182+ .map ((beanName ) -> Optional .of (context ).map (ConfigurableApplicationContext ::getBeanFactory )
183+ .map ((beanFactory ) -> beanFactory .getBeanDefinition (beanName ))
184+ .map ((beanDefinition ) -> new BeanDefinitionHolder (beanDefinition , beanName )).orElse (null ))
185+ .sorted (OrderAnnotatedBeanDefinitionComparator .INSTANCE ).map (BeanDefinitionHolder ::getBeanName )
186+ .collect (Collectors .toList ());
187+ }
188+
149189 private HttpStatus getResponseStatus (AssertableWebApplicationContext context , String path )
150190 throws IOException , javax .servlet .ServletException {
151191 FilterChainProxy filterChainProxy = context .getBean (FilterChainProxy .class );
@@ -183,6 +223,57 @@ SecurityFilterChain testSecurityFilterChain(HttpSecurity http) throws Exception
183223 .build ();
184224 }
185225
226+ @ Bean
227+ @ Order (SecurityProperties .BASIC_AUTH_ORDER - 1 )
228+ SecurityFilterChain testRemoteDevToolsSecurityFilterChain (HttpSecurity http ) throws Exception {
229+ return http .requestMatcher (new AntPathRequestMatcher ("/**" )).authorizeRequests ().anyRequest ().anonymous ()
230+ .and ().csrf ().disable ().build ();
231+ }
232+
233+ }
234+
235+ static class OrderAnnotatedBeanDefinitionComparator implements Comparator <BeanDefinitionHolder > {
236+
237+ static final OrderAnnotatedBeanDefinitionComparator INSTANCE = new OrderAnnotatedBeanDefinitionComparator ();
238+
239+ private final Map <String , Integer > beanNameToOrder = new ConcurrentHashMap <>();
240+
241+ @ Override
242+ public int compare (BeanDefinitionHolder beanOne , BeanDefinitionHolder beanTwo ) {
243+ return getOrder (beanOne ).compareTo (getOrder (beanTwo ));
244+ }
245+
246+ private Integer getOrder (BeanDefinitionHolder bean ) {
247+ return this .beanNameToOrder .computeIfAbsent (bean .getBeanName (),
248+ (beanName ) -> Optional .of (bean ).map (BeanDefinitionHolder ::getBeanDefinition )
249+ .filter (AnnotatedBeanDefinition .class ::isInstance ).map (AnnotatedBeanDefinition .class ::cast )
250+ .map (this ::getOrderAnnotationAttributesFromFactoryMethod ).map (this ::getOrder )
251+ .orElse (Ordered .LOWEST_PRECEDENCE ));
252+ }
253+
254+ private Integer getOrder (AnnotationAttributes annotationAttributes ) {
255+ return Optional .ofNullable (annotationAttributes )
256+ .map ((it ) -> it .getOrDefault ("value" , Ordered .LOWEST_PRECEDENCE )).map (Integer .class ::cast )
257+ .orElse (Ordered .LOWEST_PRECEDENCE );
258+ }
259+
260+ private AnnotationAttributes getOrderAnnotationAttributesFromFactoryMethod (
261+ AnnotatedBeanDefinition beanDefinition ) {
262+ return Optional .of (beanDefinition ).map (AnnotatedBeanDefinition ::getFactoryMethodMetadata )
263+ .filter ((methodMetadata ) -> methodMetadata .isAnnotated (Order .class .getName ()))
264+ .map ((methodMetadata ) -> methodMetadata .getAnnotationAttributes (Order .class .getName ()))
265+ .map (AnnotationAttributes ::fromMap )
266+ .orElseGet (() -> getOrderAnnotationAttributesFromBeanClass (beanDefinition ));
267+ }
268+
269+ private AnnotationAttributes getOrderAnnotationAttributesFromBeanClass (AnnotatedBeanDefinition beanDefinition ) {
270+ return Optional .of (beanDefinition ).map (AnnotatedBeanDefinition ::getResolvableType )
271+ .map (ResolvableType ::resolve ).filter ((beanType ) -> beanType .isAnnotationPresent (Order .class ))
272+ .map ((beanType ) -> beanType .getAnnotation (Order .class ))
273+ .map (AnnotationUtils ::getAnnotationAttributes ).map (AnnotationAttributes ::fromMap ).orElse (null );
274+
275+ }
276+
186277 }
187278
188279}
0 commit comments