11/*
2- * Copyright 2012-2024 the original author or authors.
2+ * Copyright 2012-2025 the original author or authors.
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License");
55 * you may not use this file except in compliance with the License.
1818
1919import java .util .Collections ;
2020import java .util .function .Function ;
21+ import java .util .function .Predicate ;
22+ import java .util .stream .Stream ;
2123
2224import org .junit .jupiter .api .Test ;
2325import org .junit .jupiter .api .extension .ExtendWith ;
26+ import org .junit .jupiter .params .ParameterizedTest ;
27+ import org .junit .jupiter .params .provider .EnumSource ;
2428
2529import 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 ;
2634import org .springframework .boot .autoconfigure .security .SecurityProperties ;
35+ import org .springframework .boot .autoconfigure .security .servlet .UserDetailsServiceAutoConfiguration .MissingAlternativeOrUserPropertiesConfigured ;
2736import org .springframework .boot .context .properties .EnableConfigurationProperties ;
2837import org .springframework .boot .test .context .FilteredClassLoader ;
2938import org .springframework .boot .test .context .runner .ApplicationContextRunner ;
3039import org .springframework .boot .test .system .CapturedOutput ;
3140import org .springframework .boot .test .system .OutputCaptureExtension ;
41+ import org .springframework .context .ConfigurableApplicationContext ;
3242import org .springframework .context .annotation .Bean ;
3343import org .springframework .context .annotation .Configuration ;
3444import org .springframework .context .annotation .Import ;
@@ -66,7 +76,8 @@ class UserDetailsServiceAutoConfigurationTests {
6676
6777 @ Test
6878 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 ();
7081 UserDetailsService manager = context .getBean (UserDetailsService .class );
7182 assertThat (output ).contains ("Using generated security password:" );
7283 assertThat (manager .loadUserByUsername ("user" )).isNotNull ();
@@ -75,60 +86,68 @@ void testDefaultUsernamePassword(CapturedOutput output) {
7586
7687 @ Test
7788 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+ });
86100 }
87101
88102 @ Test
89103 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+ });
92110 }
93111
94112 @ Test
95113 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+ });
101122 }
102123
103124 @ Test
104125 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+ });
119135 }
120136
121137 @ 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+ });
127146 }
128147
129148 @ Test
130149 void userDetailsServiceWhenPasswordEncoderAbsentAndDefaultPassword () {
131- this .contextRunner .with (noOtherFormsOfAuthenticationOnTheClasspath ())
150+ this .contextRunner .with (AlternativeFormOfAuthentication . nonPresent ())
132151 .withUserConfiguration (TestSecurityConfiguration .class )
133152 .run (((context ) -> {
134153 InMemoryUserDetailsManager userDetailsService = context .getBean (InMemoryUserDetailsManager .class );
@@ -153,56 +172,33 @@ void userDetailsServiceWhenPasswordEncoderBeanPresent() {
153172 testPasswordEncoding (TestConfigWithPasswordEncoder .class , "secret" , "secret" );
154173 }
155174
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 ));
162180 }
163181
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 ())
183187 .withPropertyValues ("spring.security.user.name=alice" )
184188 .run (((context ) -> assertThat (context ).hasSingleBean (InMemoryUserDetailsManager .class )));
185189 }
186190
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 ())
191196 .withPropertyValues ("spring.security.user.password=secret" )
192197 .run (((context ) -> assertThat (context ).hasSingleBean (InMemoryUserDetailsManager .class )));
193198 }
194199
195- private Function <ApplicationContextRunner , ApplicationContextRunner > noOtherFormsOfAuthenticationOnTheClasspath () {
196- return (contextRunner ) -> contextRunner
197- .withClassLoader (new FilteredClassLoader (ClientRegistrationRepository .class , OpaqueTokenIntrospector .class ,
198- RelyingPartyRegistrationRepository .class ));
199- }
200-
201200 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 )
206202 .withPropertyValues ("spring.security.user.password=" + providedPassword )
207203 .run (((context ) -> {
208204 InMemoryUserDetailsManager userDetailsService = context .getBean (InMemoryUserDetailsManager .class );
@@ -211,6 +207,18 @@ private void testPasswordEncoding(Class<?> configClass, String providedPassword,
211207 }));
212208 }
213209
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+
214222 @ Configuration (proxyBeanMethods = false )
215223 static class TestAuthenticationManagerConfiguration {
216224
@@ -306,4 +314,39 @@ AuthenticationManagerResolver<?> authenticationManagerResolver() {
306314
307315 }
308316
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+
309352}
0 commit comments