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,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