17
17
package org .springframework .boot .actuate .autoconfigure .security .servlet ;
18
18
19
19
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 ;
20
27
28
+ import org .jetbrains .annotations .NotNull ;
21
29
import org .junit .jupiter .api .Test ;
22
30
31
+ import org .springframework .beans .factory .annotation .AnnotatedBeanDefinition ;
32
+ import org .springframework .beans .factory .config .BeanDefinitionHolder ;
23
33
import org .springframework .boot .actuate .autoconfigure .endpoint .EndpointAutoConfiguration ;
24
34
import org .springframework .boot .actuate .autoconfigure .endpoint .web .WebEndpointAutoConfiguration ;
25
35
import org .springframework .boot .actuate .autoconfigure .env .EnvironmentEndpointAutoConfiguration ;
26
36
import org .springframework .boot .actuate .autoconfigure .health .HealthContributorAutoConfiguration ;
27
37
import org .springframework .boot .actuate .autoconfigure .health .HealthEndpointAutoConfiguration ;
28
38
import org .springframework .boot .actuate .autoconfigure .info .InfoEndpointAutoConfiguration ;
29
39
import org .springframework .boot .autoconfigure .AutoConfigurations ;
40
+ import org .springframework .boot .autoconfigure .security .SecurityProperties ;
30
41
import org .springframework .boot .autoconfigure .security .oauth2 .resource .servlet .OAuth2ResourceServerAutoConfiguration ;
31
42
import org .springframework .boot .autoconfigure .security .saml2 .Saml2RelyingPartyAutoConfiguration ;
32
43
import org .springframework .boot .autoconfigure .security .servlet .SecurityAutoConfiguration ;
33
44
import org .springframework .boot .test .context .FilteredClassLoader ;
34
45
import org .springframework .boot .test .context .assertj .AssertableWebApplicationContext ;
35
46
import org .springframework .boot .test .context .runner .WebApplicationContextRunner ;
47
+ import org .springframework .context .ConfigurableApplicationContext ;
36
48
import org .springframework .context .annotation .Bean ;
37
49
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 ;
38
55
import org .springframework .http .HttpStatus ;
39
56
import org .springframework .mock .web .MockFilterChain ;
40
57
import org .springframework .mock .web .MockHttpServletRequest ;
45
62
import org .springframework .security .config .annotation .web .configuration .WebSecurityConfigurerAdapter ;
46
63
import org .springframework .security .web .FilterChainProxy ;
47
64
import org .springframework .security .web .SecurityFilterChain ;
65
+ import org .springframework .security .web .util .matcher .AntPathRequestMatcher ;
48
66
import org .springframework .web .context .WebApplicationContext ;
49
67
50
68
import static org .assertj .core .api .Assertions .assertThat ;
@@ -121,7 +139,7 @@ void backOffIfCustomSecurityIsAdded() {
121
139
@ Test
122
140
void backsOffIfSecurityFilterChainBeanIsPresent () {
123
141
this .contextRunner .withUserConfiguration (TestSecurityFilterChainConfig .class ).run ((context ) -> {
124
- assertThat (context .getBeansOfType (SecurityFilterChain .class ). size ()). isEqualTo ( 1 );
142
+ assertThat (context .getBeansOfType (SecurityFilterChain .class )). isNotEmpty ( );
125
143
assertThat (context .containsBean ("testSecurityFilterChain" )).isTrue ();
126
144
});
127
145
}
@@ -146,6 +164,28 @@ void backOffIfSaml2RelyingPartyAutoConfigurationPresent() {
146
164
.doesNotHaveBean (MANAGEMENT_SECURITY_FILTER_CHAIN_BEAN ));
147
165
}
148
166
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
+
149
189
private HttpStatus getResponseStatus (AssertableWebApplicationContext context , String path )
150
190
throws IOException , javax .servlet .ServletException {
151
191
FilterChainProxy filterChainProxy = context .getBean (FilterChainProxy .class );
@@ -183,6 +223,57 @@ SecurityFilterChain testSecurityFilterChain(HttpSecurity http) throws Exception
183
223
.build ();
184
224
}
185
225
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
+
186
277
}
187
278
188
279
}
0 commit comments