1
1
/*
2
- * Copyright 2012-2024 the original author or authors.
2
+ * Copyright 2012-2025 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
18
18
19
19
import java .util .Collections ;
20
20
import java .util .function .Function ;
21
+ import java .util .function .Predicate ;
22
+ import java .util .stream .Stream ;
21
23
22
24
import org .junit .jupiter .api .Test ;
23
25
import org .junit .jupiter .api .extension .ExtendWith ;
26
+ import org .junit .jupiter .params .ParameterizedTest ;
27
+ import org .junit .jupiter .params .provider .EnumSource ;
24
28
25
29
import org .springframework .boot .autoconfigure .AutoConfigurations ;
30
+ import org .springframework .boot .autoconfigure .condition .ConditionEvaluationReport ;
31
+ import org .springframework .boot .autoconfigure .condition .ConditionEvaluationReport .ConditionAndOutcome ;
32
+ import org .springframework .boot .autoconfigure .condition .ConditionEvaluationReport .ConditionAndOutcomes ;
33
+ import org .springframework .boot .autoconfigure .condition .ConditionOutcome ;
26
34
import org .springframework .boot .autoconfigure .security .SecurityProperties ;
35
+ import org .springframework .boot .autoconfigure .security .servlet .UserDetailsServiceAutoConfiguration .MissingAlternativeOrUserPropertiesConfigured ;
27
36
import org .springframework .boot .context .properties .EnableConfigurationProperties ;
28
37
import org .springframework .boot .test .context .FilteredClassLoader ;
38
+ import org .springframework .boot .test .context .runner .AbstractApplicationContextRunner ;
29
39
import org .springframework .boot .test .context .runner .ApplicationContextRunner ;
30
40
import org .springframework .boot .test .context .runner .ReactiveWebApplicationContextRunner ;
31
41
import org .springframework .boot .test .context .runner .WebApplicationContextRunner ;
32
42
import org .springframework .boot .test .system .CapturedOutput ;
33
43
import org .springframework .boot .test .system .OutputCaptureExtension ;
44
+ import org .springframework .context .ConfigurableApplicationContext ;
34
45
import org .springframework .context .annotation .Bean ;
35
46
import org .springframework .context .annotation .Configuration ;
36
47
import org .springframework .context .annotation .Import ;
@@ -70,29 +81,30 @@ class UserDetailsServiceAutoConfigurationTests {
70
81
71
82
@ Test
72
83
void shouldSupplyUserDetailsServiceInServletApp () {
73
- this .contextRunner .with (AuthenticationExclude . servletApp ())
84
+ this .contextRunner .with (AlternativeFormOfAuthentication . nonPresent ())
74
85
.run ((context ) -> assertThat (context ).hasSingleBean (UserDetailsService .class ));
75
86
}
76
87
77
88
@ Test
78
89
void shouldNotSupplyUserDetailsServiceInReactiveApp () {
79
90
new ReactiveWebApplicationContextRunner ().withUserConfiguration (TestSecurityConfiguration .class )
80
91
.withConfiguration (AutoConfigurations .of (UserDetailsServiceAutoConfiguration .class ))
81
- .with (AuthenticationExclude . reactiveApp ())
92
+ .with (AlternativeFormOfAuthentication . nonPresent ())
82
93
.run ((context ) -> assertThat (context ).doesNotHaveBean (UserDetailsService .class ));
83
94
}
84
95
85
96
@ Test
86
97
void shouldNotSupplyUserDetailsServiceInNonWebApp () {
87
98
new ApplicationContextRunner ().withUserConfiguration (TestSecurityConfiguration .class )
88
99
.withConfiguration (AutoConfigurations .of (UserDetailsServiceAutoConfiguration .class ))
89
- .with (AuthenticationExclude . noWebApp ())
100
+ .with (AlternativeFormOfAuthentication . nonPresent ())
90
101
.run ((context ) -> assertThat (context ).doesNotHaveBean (UserDetailsService .class ));
91
102
}
92
103
93
104
@ Test
94
105
void testDefaultUsernamePassword (CapturedOutput output ) {
95
- this .contextRunner .with (AuthenticationExclude .servletApp ()).run ((context ) -> {
106
+ this .contextRunner .with (AlternativeFormOfAuthentication .nonPresent ()).run ((context ) -> {
107
+ assertThat (outcomeOfMissingAlternativeCondition (context ).isMatch ()).isTrue ();
96
108
UserDetailsService manager = context .getBean (UserDetailsService .class );
97
109
assertThat (output ).contains ("Using generated security password:" );
98
110
assertThat (manager .loadUserByUsername ("user" )).isNotNull ();
@@ -101,60 +113,68 @@ void testDefaultUsernamePassword(CapturedOutput output) {
101
113
102
114
@ Test
103
115
void defaultUserNotCreatedIfAuthenticationManagerBeanPresent (CapturedOutput output ) {
104
- this .contextRunner .withUserConfiguration (TestAuthenticationManagerConfiguration .class ).run ((context ) -> {
105
- AuthenticationManager manager = context .getBean (AuthenticationManager .class );
106
- assertThat (manager )
107
- .isEqualTo (context .getBean (TestAuthenticationManagerConfiguration .class ).authenticationManager );
108
- assertThat (output ).doesNotContain ("Using generated security password: " );
109
- TestingAuthenticationToken token = new TestingAuthenticationToken ("foo" , "bar" );
110
- assertThat (manager .authenticate (token )).isNotNull ();
111
- });
116
+ this .contextRunner .with (AlternativeFormOfAuthentication .nonPresent ())
117
+ .withUserConfiguration (TestAuthenticationManagerConfiguration .class )
118
+ .run ((context ) -> {
119
+ assertThat (outcomeOfMissingAlternativeCondition (context ).isMatch ()).isTrue ();
120
+ AuthenticationManager manager = context .getBean (AuthenticationManager .class );
121
+ assertThat (manager )
122
+ .isEqualTo (context .getBean (TestAuthenticationManagerConfiguration .class ).authenticationManager );
123
+ assertThat (output ).doesNotContain ("Using generated security password: " );
124
+ TestingAuthenticationToken token = new TestingAuthenticationToken ("foo" , "bar" );
125
+ assertThat (manager .authenticate (token )).isNotNull ();
126
+ });
112
127
}
113
128
114
129
@ Test
115
130
void defaultUserNotCreatedIfAuthenticationManagerResolverBeanPresent (CapturedOutput output ) {
116
- this .contextRunner .withUserConfiguration (TestAuthenticationManagerResolverConfiguration .class )
117
- .run ((context ) -> assertThat (output ).doesNotContain ("Using generated security password: " ));
131
+ this .contextRunner .with (AlternativeFormOfAuthentication .nonPresent ())
132
+ .withUserConfiguration (TestAuthenticationManagerResolverConfiguration .class )
133
+ .run ((context ) -> {
134
+ assertThat (outcomeOfMissingAlternativeCondition (context ).isMatch ()).isTrue ();
135
+ assertThat (output ).doesNotContain ("Using generated security password: " );
136
+ });
118
137
}
119
138
120
139
@ Test
121
140
void defaultUserNotCreatedIfUserDetailsServiceBeanPresent (CapturedOutput output ) {
122
- this .contextRunner .withUserConfiguration (TestUserDetailsServiceConfiguration .class ).run ((context ) -> {
123
- UserDetailsService userDetailsService = context .getBean (UserDetailsService .class );
124
- assertThat (output ).doesNotContain ("Using generated security password: " );
125
- assertThat (userDetailsService .loadUserByUsername ("foo" )).isNotNull ();
126
- });
141
+ this .contextRunner .with (AlternativeFormOfAuthentication .nonPresent ())
142
+ .withUserConfiguration (TestUserDetailsServiceConfiguration .class )
143
+ .run ((context ) -> {
144
+ assertThat (outcomeOfMissingAlternativeCondition (context ).isMatch ()).isTrue ();
145
+ UserDetailsService userDetailsService = context .getBean (UserDetailsService .class );
146
+ assertThat (output ).doesNotContain ("Using generated security password: " );
147
+ assertThat (userDetailsService .loadUserByUsername ("foo" )).isNotNull ();
148
+ });
127
149
}
128
150
129
151
@ Test
130
152
void defaultUserNotCreatedIfAuthenticationProviderBeanPresent (CapturedOutput output ) {
131
- this .contextRunner .withUserConfiguration (TestAuthenticationProviderConfiguration .class ).run ((context ) -> {
132
- AuthenticationProvider provider = context .getBean (AuthenticationProvider .class );
133
- assertThat (output ).doesNotContain ("Using generated security password: " );
134
- TestingAuthenticationToken token = new TestingAuthenticationToken ("foo" , "bar" );
135
- assertThat (provider .authenticate (token )).isNotNull ();
136
- });
137
- }
138
-
139
- @ Test
140
- void defaultUserNotCreatedIfResourceServerWithOpaqueIsUsed () {
141
- this .contextRunner .withUserConfiguration (TestConfigWithIntrospectionClient .class ).run ((context ) -> {
142
- assertThat (context ).hasSingleBean (OpaqueTokenIntrospector .class );
143
- assertThat (context ).doesNotHaveBean (UserDetailsService .class );
144
- });
153
+ this .contextRunner .with (AlternativeFormOfAuthentication .nonPresent ())
154
+ .withUserConfiguration (TestAuthenticationProviderConfiguration .class )
155
+ .run ((context ) -> {
156
+ assertThat (outcomeOfMissingAlternativeCondition (context ).isMatch ()).isTrue ();
157
+ AuthenticationProvider provider = context .getBean (AuthenticationProvider .class );
158
+ assertThat (output ).doesNotContain ("Using generated security password: " );
159
+ TestingAuthenticationToken token = new TestingAuthenticationToken ("foo" , "bar" );
160
+ assertThat (provider .authenticate (token )).isNotNull ();
161
+ });
145
162
}
146
163
147
164
@ Test
148
- void defaultUserNotCreatedIfResourceServerWithJWTIsUsed () {
149
- this .contextRunner .withUserConfiguration (TestConfigWithJwtDecoder .class ).run ((context ) -> {
150
- assertThat (context ).hasSingleBean (JwtDecoder .class );
151
- assertThat (context ).doesNotHaveBean (UserDetailsService .class );
152
- });
165
+ void defaultUserNotCreatedIfJwtDecoderBeanPresent () {
166
+ this .contextRunner .with (AlternativeFormOfAuthentication .nonPresent ())
167
+ .withUserConfiguration (TestConfigWithJwtDecoder .class )
168
+ .run ((context ) -> {
169
+ assertThat (outcomeOfMissingAlternativeCondition (context ).isMatch ()).isTrue ();
170
+ assertThat (context ).hasSingleBean (JwtDecoder .class );
171
+ assertThat (context ).doesNotHaveBean (UserDetailsService .class );
172
+ });
153
173
}
154
174
155
175
@ Test
156
176
void userDetailsServiceWhenPasswordEncoderAbsentAndDefaultPassword () {
157
- this .contextRunner .with (AuthenticationExclude . servletApp ())
177
+ this .contextRunner .with (AlternativeFormOfAuthentication . nonPresent ())
158
178
.withUserConfiguration (TestSecurityConfiguration .class )
159
179
.run (((context ) -> {
160
180
InMemoryUserDetailsManager userDetailsService = context .getBean (InMemoryUserDetailsManager .class );
@@ -179,49 +199,33 @@ void userDetailsServiceWhenPasswordEncoderBeanPresent() {
179
199
testPasswordEncoding (TestConfigWithPasswordEncoder .class , "secret" , "secret" );
180
200
}
181
201
182
- @ Test
183
- void userDetailsServiceWhenClientRegistrationRepositoryPresent () {
184
- this .contextRunner
185
- .withClassLoader (
186
- new FilteredClassLoader (OpaqueTokenIntrospector .class , RelyingPartyRegistrationRepository .class ))
187
- .run (((context ) -> assertThat (context ).doesNotHaveBean (InMemoryUserDetailsManager .class )));
188
- }
189
-
190
- @ Test
191
- void userDetailsServiceWhenOpaqueTokenIntrospectorPresent () {
192
- this .contextRunner
193
- .withClassLoader (new FilteredClassLoader (ClientRegistrationRepository .class ,
194
- RelyingPartyRegistrationRepository .class ))
195
- .run (((context ) -> assertThat (context ).doesNotHaveBean (InMemoryUserDetailsManager .class )));
196
- }
197
-
198
- @ Test
199
- void userDetailsServiceWhenRelyingPartyRegistrationRepositoryPresent () {
200
- this .contextRunner
201
- .withClassLoader (new FilteredClassLoader (ClientRegistrationRepository .class , OpaqueTokenIntrospector .class ))
202
- .run (((context ) -> assertThat (context ).doesNotHaveBean (InMemoryUserDetailsManager .class )));
202
+ @ ParameterizedTest
203
+ @ EnumSource
204
+ void whenClassOfAlternativeIsPresentUserDetailsServiceBacksOff (AlternativeFormOfAuthentication alternative ) {
205
+ this .contextRunner .with (alternative .present ())
206
+ .run ((context ) -> assertThat (context ).doesNotHaveBean (InMemoryUserDetailsManager .class ));
203
207
}
204
208
205
- @ Test
206
- void userDetailsServiceWhenRelyingPartyRegistrationRepositoryPresentAndUsernameConfigured () {
207
- this .contextRunner
208
- .withClassLoader (new FilteredClassLoader (ClientRegistrationRepository .class , OpaqueTokenIntrospector .class ))
209
+ @ ParameterizedTest
210
+ @ EnumSource
211
+ void whenAlternativeIsPresentAndUsernameIsConfiguredThenUserDetailsServiceIsAutoConfigured (
212
+ AlternativeFormOfAuthentication alternative ) {
213
+ this .contextRunner .with (alternative .present ())
209
214
.withPropertyValues ("spring.security.user.name=alice" )
210
215
.run (((context ) -> assertThat (context ).hasSingleBean (InMemoryUserDetailsManager .class )));
211
216
}
212
217
213
- @ Test
214
- void userDetailsServiceWhenRelyingPartyRegistrationRepositoryPresentAndPasswordConfigured () {
215
- this .contextRunner
216
- .withClassLoader (new FilteredClassLoader (ClientRegistrationRepository .class , OpaqueTokenIntrospector .class ))
218
+ @ ParameterizedTest
219
+ @ EnumSource
220
+ void whenAlternativeIsPresentAndPasswordIsConfiguredThenUserDetailsServiceIsAutoConfigured (
221
+ AlternativeFormOfAuthentication alternative ) {
222
+ this .contextRunner .with (alternative .present ())
217
223
.withPropertyValues ("spring.security.user.password=secret" )
218
224
.run (((context ) -> assertThat (context ).hasSingleBean (InMemoryUserDetailsManager .class )));
219
225
}
220
226
221
227
private void testPasswordEncoding (Class <?> configClass , String providedPassword , String expectedPassword ) {
222
- this .contextRunner .with (AuthenticationExclude .servletApp ())
223
- .withClassLoader (new FilteredClassLoader (ClientRegistrationRepository .class , OpaqueTokenIntrospector .class ,
224
- RelyingPartyRegistrationRepository .class ))
228
+ this .contextRunner .with (AlternativeFormOfAuthentication .nonPresent ())
225
229
.withUserConfiguration (configClass )
226
230
.withPropertyValues ("spring.security.user.password=" + providedPassword )
227
231
.run (((context ) -> {
@@ -231,24 +235,16 @@ private void testPasswordEncoding(Class<?> configClass, String providedPassword,
231
235
}));
232
236
}
233
237
234
- private static final class AuthenticationExclude {
235
-
236
- private static final FilteredClassLoader filteredClassLoader = new FilteredClassLoader (
237
- ClientRegistrationRepository . class , OpaqueTokenIntrospector .class ,
238
- RelyingPartyRegistrationRepository . class );
239
-
240
- static Function < WebApplicationContextRunner , WebApplicationContextRunner > servletApp () {
241
- return ( contextRunner ) -> contextRunner . withClassLoader ( filteredClassLoader );
238
+ private ConditionOutcome outcomeOfMissingAlternativeCondition ( ConfigurableApplicationContext context ) {
239
+ ConditionAndOutcomes conditionAndOutcomes = ConditionEvaluationReport . get ( context . getBeanFactory ())
240
+ . getConditionAndOutcomesBySource ()
241
+ . get ( UserDetailsServiceAutoConfiguration .class . getName ());
242
+ for ( ConditionAndOutcome conditionAndOutcome : conditionAndOutcomes ) {
243
+ if ( conditionAndOutcome . getCondition () instanceof MissingAlternativeOrUserPropertiesConfigured ) {
244
+ return conditionAndOutcome . getOutcome ();
245
+ }
242
246
}
243
-
244
- static Function <ReactiveWebApplicationContextRunner , ReactiveWebApplicationContextRunner > reactiveApp () {
245
- return (contextRunner ) -> contextRunner .withClassLoader (filteredClassLoader );
246
- }
247
-
248
- static Function <ApplicationContextRunner , ApplicationContextRunner > noWebApp () {
249
- return (contextRunner ) -> contextRunner .withClassLoader (filteredClassLoader );
250
- }
251
-
247
+ return null ;
252
248
}
253
249
254
250
@ Configuration (proxyBeanMethods = false )
@@ -346,4 +342,41 @@ AuthenticationManagerResolver<?> authenticationManagerResolver() {
346
342
347
343
}
348
344
345
+ private enum AlternativeFormOfAuthentication {
346
+
347
+ CLIENT_REGISTRATION_REPOSITORY (ClientRegistrationRepository .class ),
348
+
349
+ OPAQUE_TOKEN_INTROSPECTOR (OpaqueTokenIntrospector .class ),
350
+
351
+ RELYING_PARTY_REGISTRATION_REPOSITORY (RelyingPartyRegistrationRepository .class );
352
+
353
+ private final Class <?> type ;
354
+
355
+ AlternativeFormOfAuthentication (Class <?> type ) {
356
+ this .type = type ;
357
+ }
358
+
359
+ private Class <?> getType () {
360
+ return this .type ;
361
+ }
362
+
363
+ @ SuppressWarnings ("unchecked" )
364
+ private <T extends AbstractApplicationContextRunner <?, ?, ?>> Function <T , T > present () {
365
+ return (contextRunner ) -> (T ) contextRunner
366
+ .withClassLoader (new FilteredClassLoader (Stream .of (AlternativeFormOfAuthentication .values ())
367
+ .filter (Predicate .not (this ::equals ))
368
+ .map (AlternativeFormOfAuthentication ::getType )
369
+ .toArray (Class []::new )));
370
+ }
371
+
372
+ @ SuppressWarnings ("unchecked" )
373
+ private static <T extends AbstractApplicationContextRunner <?, ?, ?>> Function <T , T > nonPresent () {
374
+ return (contextRunner ) -> (T ) contextRunner
375
+ .withClassLoader (new FilteredClassLoader (Stream .of (AlternativeFormOfAuthentication .values ())
376
+ .map (AlternativeFormOfAuthentication ::getType )
377
+ .toArray (Class []::new )));
378
+ }
379
+
380
+ }
381
+
349
382
}
0 commit comments