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 ;
@@ -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