Skip to content

Commit 96ffb2b

Browse files
committed
Merge branch '3.3.x' into 3.4.x
Closes gh-44940
2 parents 16e4449 + bed6ad3 commit 96ffb2b

File tree

1 file changed

+122
-89
lines changed

1 file changed

+122
-89
lines changed

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

+122-89
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,19 +18,30 @@
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;
38+
import org.springframework.boot.test.context.runner.AbstractApplicationContextRunner;
2939
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
3040
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
3141
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
3242
import org.springframework.boot.test.system.CapturedOutput;
3343
import org.springframework.boot.test.system.OutputCaptureExtension;
44+
import org.springframework.context.ConfigurableApplicationContext;
3445
import org.springframework.context.annotation.Bean;
3546
import org.springframework.context.annotation.Configuration;
3647
import org.springframework.context.annotation.Import;
@@ -70,29 +81,30 @@ class UserDetailsServiceAutoConfigurationTests {
7081

7182
@Test
7283
void shouldSupplyUserDetailsServiceInServletApp() {
73-
this.contextRunner.with(AuthenticationExclude.servletApp())
84+
this.contextRunner.with(AlternativeFormOfAuthentication.nonPresent())
7485
.run((context) -> assertThat(context).hasSingleBean(UserDetailsService.class));
7586
}
7687

7788
@Test
7889
void shouldNotSupplyUserDetailsServiceInReactiveApp() {
7990
new ReactiveWebApplicationContextRunner().withUserConfiguration(TestSecurityConfiguration.class)
8091
.withConfiguration(AutoConfigurations.of(UserDetailsServiceAutoConfiguration.class))
81-
.with(AuthenticationExclude.reactiveApp())
92+
.with(AlternativeFormOfAuthentication.nonPresent())
8293
.run((context) -> assertThat(context).doesNotHaveBean(UserDetailsService.class));
8394
}
8495

8596
@Test
8697
void shouldNotSupplyUserDetailsServiceInNonWebApp() {
8798
new ApplicationContextRunner().withUserConfiguration(TestSecurityConfiguration.class)
8899
.withConfiguration(AutoConfigurations.of(UserDetailsServiceAutoConfiguration.class))
89-
.with(AuthenticationExclude.noWebApp())
100+
.with(AlternativeFormOfAuthentication.nonPresent())
90101
.run((context) -> assertThat(context).doesNotHaveBean(UserDetailsService.class));
91102
}
92103

93104
@Test
94105
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();
96108
UserDetailsService manager = context.getBean(UserDetailsService.class);
97109
assertThat(output).contains("Using generated security password:");
98110
assertThat(manager.loadUserByUsername("user")).isNotNull();
@@ -101,60 +113,68 @@ void testDefaultUsernamePassword(CapturedOutput output) {
101113

102114
@Test
103115
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+
});
112127
}
113128

114129
@Test
115130
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+
});
118137
}
119138

120139
@Test
121140
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+
});
127149
}
128150

129151
@Test
130152
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+
});
145162
}
146163

147164
@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+
});
153173
}
154174

155175
@Test
156176
void userDetailsServiceWhenPasswordEncoderAbsentAndDefaultPassword() {
157-
this.contextRunner.with(AuthenticationExclude.servletApp())
177+
this.contextRunner.with(AlternativeFormOfAuthentication.nonPresent())
158178
.withUserConfiguration(TestSecurityConfiguration.class)
159179
.run(((context) -> {
160180
InMemoryUserDetailsManager userDetailsService = context.getBean(InMemoryUserDetailsManager.class);
@@ -179,49 +199,33 @@ void userDetailsServiceWhenPasswordEncoderBeanPresent() {
179199
testPasswordEncoding(TestConfigWithPasswordEncoder.class, "secret", "secret");
180200
}
181201

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));
203207
}
204208

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())
209214
.withPropertyValues("spring.security.user.name=alice")
210215
.run(((context) -> assertThat(context).hasSingleBean(InMemoryUserDetailsManager.class)));
211216
}
212217

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())
217223
.withPropertyValues("spring.security.user.password=secret")
218224
.run(((context) -> assertThat(context).hasSingleBean(InMemoryUserDetailsManager.class)));
219225
}
220226

221227
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())
225229
.withUserConfiguration(configClass)
226230
.withPropertyValues("spring.security.user.password=" + providedPassword)
227231
.run(((context) -> {
@@ -231,24 +235,16 @@ private void testPasswordEncoding(Class<?> configClass, String providedPassword,
231235
}));
232236
}
233237

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+
}
242246
}
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;
252248
}
253249

254250
@Configuration(proxyBeanMethods = false)
@@ -346,4 +342,41 @@ AuthenticationManagerResolver<?> authenticationManagerResolver() {
346342

347343
}
348344

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+
349382
}

0 commit comments

Comments
 (0)