Skip to content

Commit bed6ad3

Browse files
committed
Polish UserDetailsServiceAutoConfigurationTests
Closes gh-44939
1 parent aab929d commit bed6ad3

File tree

1 file changed

+119
-76
lines changed

1 file changed

+119
-76
lines changed

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/servlet/UserDetailsServiceAutoConfigurationTests.java

+119-76
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
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.
@@ -18,17 +18,27 @@
1818

1919
import java.util.Collections;
2020
import java.util.function.Function;
21+
import java.util.function.Predicate;
22+
import java.util.stream.Stream;
2123

2224
import org.junit.jupiter.api.Test;
2325
import org.junit.jupiter.api.extension.ExtendWith;
26+
import org.junit.jupiter.params.ParameterizedTest;
27+
import org.junit.jupiter.params.provider.EnumSource;
2428

2529
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;
2634
import org.springframework.boot.autoconfigure.security.SecurityProperties;
35+
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration.MissingAlternativeOrUserPropertiesConfigured;
2736
import org.springframework.boot.context.properties.EnableConfigurationProperties;
2837
import org.springframework.boot.test.context.FilteredClassLoader;
2938
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
3039
import org.springframework.boot.test.system.CapturedOutput;
3140
import org.springframework.boot.test.system.OutputCaptureExtension;
41+
import org.springframework.context.ConfigurableApplicationContext;
3242
import org.springframework.context.annotation.Bean;
3343
import org.springframework.context.annotation.Configuration;
3444
import 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

Comments
 (0)