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 ;
29
38
import org .springframework .boot .test .context .runner .ApplicationContextRunner ;
30
39
import org .springframework .boot .test .system .CapturedOutput ;
31
40
import org .springframework .boot .test .system .OutputCaptureExtension ;
41
+ import org .springframework .context .ConfigurableApplicationContext ;
32
42
import org .springframework .context .annotation .Bean ;
33
43
import org .springframework .context .annotation .Configuration ;
34
44
import org .springframework .context .annotation .Import ;
@@ -66,7 +76,8 @@ class UserDetailsServiceAutoConfigurationTests {
66
76
67
77
@ Test
68
78
void testDefaultUsernamePassword (CapturedOutput output ) {
69
- this .contextRunner .with (noOtherFormsOfAuthenticationOnTheClasspath ()).run ((context ) -> {
79
+ this .contextRunner .with (AlternativeFormOfAuthentication .nonPresent ()).run ((context ) -> {
80
+ assertThat (outcomeOfMissingAlternativeCondition (context ).isMatch ()).isTrue ();
70
81
UserDetailsService manager = context .getBean (UserDetailsService .class );
71
82
assertThat (output ).contains ("Using generated security password:" );
72
83
assertThat (manager .loadUserByUsername ("user" )).isNotNull ();
@@ -75,60 +86,68 @@ void testDefaultUsernamePassword(CapturedOutput output) {
75
86
76
87
@ Test
77
88
void defaultUserNotCreatedIfAuthenticationManagerBeanPresent (CapturedOutput output ) {
78
- this .contextRunner .withUserConfiguration (TestAuthenticationManagerConfiguration .class ).run ((context ) -> {
79
- AuthenticationManager manager = context .getBean (AuthenticationManager .class );
80
- assertThat (manager )
81
- .isEqualTo (context .getBean (TestAuthenticationManagerConfiguration .class ).authenticationManager );
82
- assertThat (output ).doesNotContain ("Using generated security password: " );
83
- TestingAuthenticationToken token = new TestingAuthenticationToken ("foo" , "bar" );
84
- assertThat (manager .authenticate (token )).isNotNull ();
85
- });
89
+ this .contextRunner .with (AlternativeFormOfAuthentication .nonPresent ())
90
+ .withUserConfiguration (TestAuthenticationManagerConfiguration .class )
91
+ .run ((context ) -> {
92
+ assertThat (outcomeOfMissingAlternativeCondition (context ).isMatch ()).isTrue ();
93
+ AuthenticationManager manager = context .getBean (AuthenticationManager .class );
94
+ assertThat (manager )
95
+ .isEqualTo (context .getBean (TestAuthenticationManagerConfiguration .class ).authenticationManager );
96
+ assertThat (output ).doesNotContain ("Using generated security password: " );
97
+ TestingAuthenticationToken token = new TestingAuthenticationToken ("foo" , "bar" );
98
+ assertThat (manager .authenticate (token )).isNotNull ();
99
+ });
86
100
}
87
101
88
102
@ Test
89
103
void defaultUserNotCreatedIfAuthenticationManagerResolverBeanPresent (CapturedOutput output ) {
90
- this .contextRunner .withUserConfiguration (TestAuthenticationManagerResolverConfiguration .class )
91
- .run ((context ) -> assertThat (output ).doesNotContain ("Using generated security password: " ));
104
+ this .contextRunner .with (AlternativeFormOfAuthentication .nonPresent ())
105
+ .withUserConfiguration (TestAuthenticationManagerResolverConfiguration .class )
106
+ .run ((context ) -> {
107
+ assertThat (outcomeOfMissingAlternativeCondition (context ).isMatch ()).isTrue ();
108
+ assertThat (output ).doesNotContain ("Using generated security password: " );
109
+ });
92
110
}
93
111
94
112
@ Test
95
113
void defaultUserNotCreatedIfUserDetailsServiceBeanPresent (CapturedOutput output ) {
96
- this .contextRunner .withUserConfiguration (TestUserDetailsServiceConfiguration .class ).run ((context ) -> {
97
- UserDetailsService userDetailsService = context .getBean (UserDetailsService .class );
98
- assertThat (output ).doesNotContain ("Using generated security password: " );
99
- assertThat (userDetailsService .loadUserByUsername ("foo" )).isNotNull ();
100
- });
114
+ this .contextRunner .with (AlternativeFormOfAuthentication .nonPresent ())
115
+ .withUserConfiguration (TestUserDetailsServiceConfiguration .class )
116
+ .run ((context ) -> {
117
+ assertThat (outcomeOfMissingAlternativeCondition (context ).isMatch ()).isTrue ();
118
+ UserDetailsService userDetailsService = context .getBean (UserDetailsService .class );
119
+ assertThat (output ).doesNotContain ("Using generated security password: " );
120
+ assertThat (userDetailsService .loadUserByUsername ("foo" )).isNotNull ();
121
+ });
101
122
}
102
123
103
124
@ Test
104
125
void defaultUserNotCreatedIfAuthenticationProviderBeanPresent (CapturedOutput output ) {
105
- this .contextRunner .withUserConfiguration (TestAuthenticationProviderConfiguration .class ).run ((context ) -> {
106
- AuthenticationProvider provider = context .getBean (AuthenticationProvider .class );
107
- assertThat (output ).doesNotContain ("Using generated security password: " );
108
- TestingAuthenticationToken token = new TestingAuthenticationToken ("foo" , "bar" );
109
- assertThat (provider .authenticate (token )).isNotNull ();
110
- });
111
- }
112
-
113
- @ Test
114
- void defaultUserNotCreatedIfResourceServerWithOpaqueIsUsed () {
115
- this .contextRunner .withUserConfiguration (TestConfigWithIntrospectionClient .class ).run ((context ) -> {
116
- assertThat (context ).hasSingleBean (OpaqueTokenIntrospector .class );
117
- assertThat (context ).doesNotHaveBean (UserDetailsService .class );
118
- });
126
+ this .contextRunner .with (AlternativeFormOfAuthentication .nonPresent ())
127
+ .withUserConfiguration (TestAuthenticationProviderConfiguration .class )
128
+ .run ((context ) -> {
129
+ assertThat (outcomeOfMissingAlternativeCondition (context ).isMatch ()).isTrue ();
130
+ AuthenticationProvider provider = context .getBean (AuthenticationProvider .class );
131
+ assertThat (output ).doesNotContain ("Using generated security password: " );
132
+ TestingAuthenticationToken token = new TestingAuthenticationToken ("foo" , "bar" );
133
+ assertThat (provider .authenticate (token )).isNotNull ();
134
+ });
119
135
}
120
136
121
137
@ Test
122
- void defaultUserNotCreatedIfResourceServerWithJWTIsUsed () {
123
- this .contextRunner .withUserConfiguration (TestConfigWithJwtDecoder .class ).run ((context ) -> {
124
- assertThat (context ).hasSingleBean (JwtDecoder .class );
125
- assertThat (context ).doesNotHaveBean (UserDetailsService .class );
126
- });
138
+ void defaultUserNotCreatedIfJwtDecoderBeanPresent () {
139
+ this .contextRunner .with (AlternativeFormOfAuthentication .nonPresent ())
140
+ .withUserConfiguration (TestConfigWithJwtDecoder .class )
141
+ .run ((context ) -> {
142
+ assertThat (outcomeOfMissingAlternativeCondition (context ).isMatch ()).isTrue ();
143
+ assertThat (context ).hasSingleBean (JwtDecoder .class );
144
+ assertThat (context ).doesNotHaveBean (UserDetailsService .class );
145
+ });
127
146
}
128
147
129
148
@ Test
130
149
void userDetailsServiceWhenPasswordEncoderAbsentAndDefaultPassword () {
131
- this .contextRunner .with (noOtherFormsOfAuthenticationOnTheClasspath ())
150
+ this .contextRunner .with (AlternativeFormOfAuthentication . nonPresent ())
132
151
.withUserConfiguration (TestSecurityConfiguration .class )
133
152
.run (((context ) -> {
134
153
InMemoryUserDetailsManager userDetailsService = context .getBean (InMemoryUserDetailsManager .class );
@@ -153,56 +172,33 @@ void userDetailsServiceWhenPasswordEncoderBeanPresent() {
153
172
testPasswordEncoding (TestConfigWithPasswordEncoder .class , "secret" , "secret" );
154
173
}
155
174
156
- @ Test
157
- void userDetailsServiceWhenClientRegistrationRepositoryPresent () {
158
- this .contextRunner
159
- .withClassLoader (
160
- new FilteredClassLoader (OpaqueTokenIntrospector .class , RelyingPartyRegistrationRepository .class ))
161
- .run (((context ) -> assertThat (context ).doesNotHaveBean (InMemoryUserDetailsManager .class )));
175
+ @ ParameterizedTest
176
+ @ EnumSource
177
+ void whenClassOfAlternativeIsPresentUserDetailsServiceBacksOff (AlternativeFormOfAuthentication alternative ) {
178
+ this .contextRunner .with (alternative .present ())
179
+ .run ((context ) -> assertThat (context ).doesNotHaveBean (InMemoryUserDetailsManager .class ));
162
180
}
163
181
164
- @ Test
165
- void userDetailsServiceWhenOpaqueTokenIntrospectorPresent () {
166
- this .contextRunner
167
- .withClassLoader (new FilteredClassLoader (ClientRegistrationRepository .class ,
168
- RelyingPartyRegistrationRepository .class ))
169
- .run (((context ) -> assertThat (context ).doesNotHaveBean (InMemoryUserDetailsManager .class )));
170
- }
171
-
172
- @ Test
173
- void userDetailsServiceWhenRelyingPartyRegistrationRepositoryPresent () {
174
- this .contextRunner
175
- .withClassLoader (new FilteredClassLoader (ClientRegistrationRepository .class , OpaqueTokenIntrospector .class ))
176
- .run (((context ) -> assertThat (context ).doesNotHaveBean (InMemoryUserDetailsManager .class )));
177
- }
178
-
179
- @ Test
180
- void userDetailsServiceWhenRelyingPartyRegistrationRepositoryPresentAndUsernameConfigured () {
181
- this .contextRunner
182
- .withClassLoader (new FilteredClassLoader (ClientRegistrationRepository .class , OpaqueTokenIntrospector .class ))
182
+ @ ParameterizedTest
183
+ @ EnumSource
184
+ void whenAlternativeIsPresentAndUsernameIsConfiguredThenUserDetailsServiceIsAutoConfigured (
185
+ AlternativeFormOfAuthentication alternative ) {
186
+ this .contextRunner .with (alternative .present ())
183
187
.withPropertyValues ("spring.security.user.name=alice" )
184
188
.run (((context ) -> assertThat (context ).hasSingleBean (InMemoryUserDetailsManager .class )));
185
189
}
186
190
187
- @ Test
188
- void userDetailsServiceWhenRelyingPartyRegistrationRepositoryPresentAndPasswordConfigured () {
189
- this .contextRunner
190
- .withClassLoader (new FilteredClassLoader (ClientRegistrationRepository .class , OpaqueTokenIntrospector .class ))
191
+ @ ParameterizedTest
192
+ @ EnumSource
193
+ void whenAlternativeIsPresentAndPasswordIsConfiguredThenUserDetailsServiceIsAutoConfigured (
194
+ AlternativeFormOfAuthentication alternative ) {
195
+ this .contextRunner .with (alternative .present ())
191
196
.withPropertyValues ("spring.security.user.password=secret" )
192
197
.run (((context ) -> assertThat (context ).hasSingleBean (InMemoryUserDetailsManager .class )));
193
198
}
194
199
195
- private Function <ApplicationContextRunner , ApplicationContextRunner > noOtherFormsOfAuthenticationOnTheClasspath () {
196
- return (contextRunner ) -> contextRunner
197
- .withClassLoader (new FilteredClassLoader (ClientRegistrationRepository .class , OpaqueTokenIntrospector .class ,
198
- RelyingPartyRegistrationRepository .class ));
199
- }
200
-
201
200
private void testPasswordEncoding (Class <?> configClass , String providedPassword , String expectedPassword ) {
202
- this .contextRunner .with (noOtherFormsOfAuthenticationOnTheClasspath ())
203
- .withClassLoader (new FilteredClassLoader (ClientRegistrationRepository .class , OpaqueTokenIntrospector .class ,
204
- RelyingPartyRegistrationRepository .class ))
205
- .withUserConfiguration (configClass )
201
+ this .contextRunner .withUserConfiguration (configClass )
206
202
.withPropertyValues ("spring.security.user.password=" + providedPassword )
207
203
.run (((context ) -> {
208
204
InMemoryUserDetailsManager userDetailsService = context .getBean (InMemoryUserDetailsManager .class );
@@ -211,6 +207,18 @@ private void testPasswordEncoding(Class<?> configClass, String providedPassword,
211
207
}));
212
208
}
213
209
210
+ private ConditionOutcome outcomeOfMissingAlternativeCondition (ConfigurableApplicationContext context ) {
211
+ ConditionAndOutcomes conditionAndOutcomes = ConditionEvaluationReport .get (context .getBeanFactory ())
212
+ .getConditionAndOutcomesBySource ()
213
+ .get (UserDetailsServiceAutoConfiguration .class .getName ());
214
+ for (ConditionAndOutcome conditionAndOutcome : conditionAndOutcomes ) {
215
+ if (conditionAndOutcome .getCondition () instanceof MissingAlternativeOrUserPropertiesConfigured ) {
216
+ return conditionAndOutcome .getOutcome ();
217
+ }
218
+ }
219
+ return null ;
220
+ }
221
+
214
222
@ Configuration (proxyBeanMethods = false )
215
223
static class TestAuthenticationManagerConfiguration {
216
224
@@ -306,4 +314,39 @@ AuthenticationManagerResolver<?> authenticationManagerResolver() {
306
314
307
315
}
308
316
317
+ private enum AlternativeFormOfAuthentication {
318
+
319
+ CLIENT_REGISTRATION_REPOSITORY (ClientRegistrationRepository .class ),
320
+
321
+ OPAQUE_TOKEN_INTROSPECTOR (OpaqueTokenIntrospector .class ),
322
+
323
+ RELYING_PARTY_REGISTRATION_REPOSITORY (RelyingPartyRegistrationRepository .class );
324
+
325
+ private final Class <?> type ;
326
+
327
+ AlternativeFormOfAuthentication (Class <?> type ) {
328
+ this .type = type ;
329
+ }
330
+
331
+ private Class <?> getType () {
332
+ return this .type ;
333
+ }
334
+
335
+ private Function <ApplicationContextRunner , ApplicationContextRunner > present () {
336
+ return (contextRunner ) -> contextRunner
337
+ .withClassLoader (new FilteredClassLoader (Stream .of (AlternativeFormOfAuthentication .values ())
338
+ .filter (Predicate .not (this ::equals ))
339
+ .map (AlternativeFormOfAuthentication ::getType )
340
+ .toArray (Class []::new )));
341
+ }
342
+
343
+ private static Function <ApplicationContextRunner , ApplicationContextRunner > nonPresent () {
344
+ return (contextRunner ) -> contextRunner
345
+ .withClassLoader (new FilteredClassLoader (Stream .of (AlternativeFormOfAuthentication .values ())
346
+ .map (AlternativeFormOfAuthentication ::getType )
347
+ .toArray (Class []::new )));
348
+ }
349
+
350
+ }
351
+
309
352
}
0 commit comments