mirror of
https://github.com/spring-projects/spring-boot.git
synced 2026-05-02 19:30:23 +01:00
Polish UserDetailsServiceAutoConfigurationTests
Closes gh-44939
This commit is contained in:
+119
-76
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2024 the original author or authors.
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,17 +18,27 @@ package org.springframework.boot.autoconfigure.security.servlet;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport.ConditionAndOutcome;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport.ConditionAndOutcomes;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
|
||||
import org.springframework.boot.autoconfigure.security.SecurityProperties;
|
||||
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration.MissingAlternativeOrUserPropertiesConfigured;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.boot.test.system.CapturedOutput;
|
||||
import org.springframework.boot.test.system.OutputCaptureExtension;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
@@ -66,7 +76,8 @@ class UserDetailsServiceAutoConfigurationTests {
|
||||
|
||||
@Test
|
||||
void testDefaultUsernamePassword(CapturedOutput output) {
|
||||
this.contextRunner.with(noOtherFormsOfAuthenticationOnTheClasspath()).run((context) -> {
|
||||
this.contextRunner.with(AlternativeFormOfAuthentication.nonPresent()).run((context) -> {
|
||||
assertThat(outcomeOfMissingAlternativeCondition(context).isMatch()).isTrue();
|
||||
UserDetailsService manager = context.getBean(UserDetailsService.class);
|
||||
assertThat(output).contains("Using generated security password:");
|
||||
assertThat(manager.loadUserByUsername("user")).isNotNull();
|
||||
@@ -75,60 +86,68 @@ class UserDetailsServiceAutoConfigurationTests {
|
||||
|
||||
@Test
|
||||
void defaultUserNotCreatedIfAuthenticationManagerBeanPresent(CapturedOutput output) {
|
||||
this.contextRunner.withUserConfiguration(TestAuthenticationManagerConfiguration.class).run((context) -> {
|
||||
AuthenticationManager manager = context.getBean(AuthenticationManager.class);
|
||||
assertThat(manager)
|
||||
.isEqualTo(context.getBean(TestAuthenticationManagerConfiguration.class).authenticationManager);
|
||||
assertThat(output).doesNotContain("Using generated security password: ");
|
||||
TestingAuthenticationToken token = new TestingAuthenticationToken("foo", "bar");
|
||||
assertThat(manager.authenticate(token)).isNotNull();
|
||||
});
|
||||
this.contextRunner.with(AlternativeFormOfAuthentication.nonPresent())
|
||||
.withUserConfiguration(TestAuthenticationManagerConfiguration.class)
|
||||
.run((context) -> {
|
||||
assertThat(outcomeOfMissingAlternativeCondition(context).isMatch()).isTrue();
|
||||
AuthenticationManager manager = context.getBean(AuthenticationManager.class);
|
||||
assertThat(manager)
|
||||
.isEqualTo(context.getBean(TestAuthenticationManagerConfiguration.class).authenticationManager);
|
||||
assertThat(output).doesNotContain("Using generated security password: ");
|
||||
TestingAuthenticationToken token = new TestingAuthenticationToken("foo", "bar");
|
||||
assertThat(manager.authenticate(token)).isNotNull();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultUserNotCreatedIfAuthenticationManagerResolverBeanPresent(CapturedOutput output) {
|
||||
this.contextRunner.withUserConfiguration(TestAuthenticationManagerResolverConfiguration.class)
|
||||
.run((context) -> assertThat(output).doesNotContain("Using generated security password: "));
|
||||
this.contextRunner.with(AlternativeFormOfAuthentication.nonPresent())
|
||||
.withUserConfiguration(TestAuthenticationManagerResolverConfiguration.class)
|
||||
.run((context) -> {
|
||||
assertThat(outcomeOfMissingAlternativeCondition(context).isMatch()).isTrue();
|
||||
assertThat(output).doesNotContain("Using generated security password: ");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultUserNotCreatedIfUserDetailsServiceBeanPresent(CapturedOutput output) {
|
||||
this.contextRunner.withUserConfiguration(TestUserDetailsServiceConfiguration.class).run((context) -> {
|
||||
UserDetailsService userDetailsService = context.getBean(UserDetailsService.class);
|
||||
assertThat(output).doesNotContain("Using generated security password: ");
|
||||
assertThat(userDetailsService.loadUserByUsername("foo")).isNotNull();
|
||||
});
|
||||
this.contextRunner.with(AlternativeFormOfAuthentication.nonPresent())
|
||||
.withUserConfiguration(TestUserDetailsServiceConfiguration.class)
|
||||
.run((context) -> {
|
||||
assertThat(outcomeOfMissingAlternativeCondition(context).isMatch()).isTrue();
|
||||
UserDetailsService userDetailsService = context.getBean(UserDetailsService.class);
|
||||
assertThat(output).doesNotContain("Using generated security password: ");
|
||||
assertThat(userDetailsService.loadUserByUsername("foo")).isNotNull();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultUserNotCreatedIfAuthenticationProviderBeanPresent(CapturedOutput output) {
|
||||
this.contextRunner.withUserConfiguration(TestAuthenticationProviderConfiguration.class).run((context) -> {
|
||||
AuthenticationProvider provider = context.getBean(AuthenticationProvider.class);
|
||||
assertThat(output).doesNotContain("Using generated security password: ");
|
||||
TestingAuthenticationToken token = new TestingAuthenticationToken("foo", "bar");
|
||||
assertThat(provider.authenticate(token)).isNotNull();
|
||||
});
|
||||
this.contextRunner.with(AlternativeFormOfAuthentication.nonPresent())
|
||||
.withUserConfiguration(TestAuthenticationProviderConfiguration.class)
|
||||
.run((context) -> {
|
||||
assertThat(outcomeOfMissingAlternativeCondition(context).isMatch()).isTrue();
|
||||
AuthenticationProvider provider = context.getBean(AuthenticationProvider.class);
|
||||
assertThat(output).doesNotContain("Using generated security password: ");
|
||||
TestingAuthenticationToken token = new TestingAuthenticationToken("foo", "bar");
|
||||
assertThat(provider.authenticate(token)).isNotNull();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultUserNotCreatedIfResourceServerWithOpaqueIsUsed() {
|
||||
this.contextRunner.withUserConfiguration(TestConfigWithIntrospectionClient.class).run((context) -> {
|
||||
assertThat(context).hasSingleBean(OpaqueTokenIntrospector.class);
|
||||
assertThat(context).doesNotHaveBean(UserDetailsService.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultUserNotCreatedIfResourceServerWithJWTIsUsed() {
|
||||
this.contextRunner.withUserConfiguration(TestConfigWithJwtDecoder.class).run((context) -> {
|
||||
assertThat(context).hasSingleBean(JwtDecoder.class);
|
||||
assertThat(context).doesNotHaveBean(UserDetailsService.class);
|
||||
});
|
||||
void defaultUserNotCreatedIfJwtDecoderBeanPresent() {
|
||||
this.contextRunner.with(AlternativeFormOfAuthentication.nonPresent())
|
||||
.withUserConfiguration(TestConfigWithJwtDecoder.class)
|
||||
.run((context) -> {
|
||||
assertThat(outcomeOfMissingAlternativeCondition(context).isMatch()).isTrue();
|
||||
assertThat(context).hasSingleBean(JwtDecoder.class);
|
||||
assertThat(context).doesNotHaveBean(UserDetailsService.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void userDetailsServiceWhenPasswordEncoderAbsentAndDefaultPassword() {
|
||||
this.contextRunner.with(noOtherFormsOfAuthenticationOnTheClasspath())
|
||||
this.contextRunner.with(AlternativeFormOfAuthentication.nonPresent())
|
||||
.withUserConfiguration(TestSecurityConfiguration.class)
|
||||
.run(((context) -> {
|
||||
InMemoryUserDetailsManager userDetailsService = context.getBean(InMemoryUserDetailsManager.class);
|
||||
@@ -153,56 +172,33 @@ class UserDetailsServiceAutoConfigurationTests {
|
||||
testPasswordEncoding(TestConfigWithPasswordEncoder.class, "secret", "secret");
|
||||
}
|
||||
|
||||
@Test
|
||||
void userDetailsServiceWhenClientRegistrationRepositoryPresent() {
|
||||
this.contextRunner
|
||||
.withClassLoader(
|
||||
new FilteredClassLoader(OpaqueTokenIntrospector.class, RelyingPartyRegistrationRepository.class))
|
||||
.run(((context) -> assertThat(context).doesNotHaveBean(InMemoryUserDetailsManager.class)));
|
||||
@ParameterizedTest
|
||||
@EnumSource
|
||||
void whenClassOfAlternativeIsPresentUserDetailsServiceBacksOff(AlternativeFormOfAuthentication alternative) {
|
||||
this.contextRunner.with(alternative.present())
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(InMemoryUserDetailsManager.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void userDetailsServiceWhenOpaqueTokenIntrospectorPresent() {
|
||||
this.contextRunner
|
||||
.withClassLoader(new FilteredClassLoader(ClientRegistrationRepository.class,
|
||||
RelyingPartyRegistrationRepository.class))
|
||||
.run(((context) -> assertThat(context).doesNotHaveBean(InMemoryUserDetailsManager.class)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void userDetailsServiceWhenRelyingPartyRegistrationRepositoryPresent() {
|
||||
this.contextRunner
|
||||
.withClassLoader(new FilteredClassLoader(ClientRegistrationRepository.class, OpaqueTokenIntrospector.class))
|
||||
.run(((context) -> assertThat(context).doesNotHaveBean(InMemoryUserDetailsManager.class)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void userDetailsServiceWhenRelyingPartyRegistrationRepositoryPresentAndUsernameConfigured() {
|
||||
this.contextRunner
|
||||
.withClassLoader(new FilteredClassLoader(ClientRegistrationRepository.class, OpaqueTokenIntrospector.class))
|
||||
@ParameterizedTest
|
||||
@EnumSource
|
||||
void whenAlternativeIsPresentAndUsernameIsConfiguredThenUserDetailsServiceIsAutoConfigured(
|
||||
AlternativeFormOfAuthentication alternative) {
|
||||
this.contextRunner.with(alternative.present())
|
||||
.withPropertyValues("spring.security.user.name=alice")
|
||||
.run(((context) -> assertThat(context).hasSingleBean(InMemoryUserDetailsManager.class)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void userDetailsServiceWhenRelyingPartyRegistrationRepositoryPresentAndPasswordConfigured() {
|
||||
this.contextRunner
|
||||
.withClassLoader(new FilteredClassLoader(ClientRegistrationRepository.class, OpaqueTokenIntrospector.class))
|
||||
@ParameterizedTest
|
||||
@EnumSource
|
||||
void whenAlternativeIsPresentAndPasswordIsConfiguredThenUserDetailsServiceIsAutoConfigured(
|
||||
AlternativeFormOfAuthentication alternative) {
|
||||
this.contextRunner.with(alternative.present())
|
||||
.withPropertyValues("spring.security.user.password=secret")
|
||||
.run(((context) -> assertThat(context).hasSingleBean(InMemoryUserDetailsManager.class)));
|
||||
}
|
||||
|
||||
private Function<ApplicationContextRunner, ApplicationContextRunner> noOtherFormsOfAuthenticationOnTheClasspath() {
|
||||
return (contextRunner) -> contextRunner
|
||||
.withClassLoader(new FilteredClassLoader(ClientRegistrationRepository.class, OpaqueTokenIntrospector.class,
|
||||
RelyingPartyRegistrationRepository.class));
|
||||
}
|
||||
|
||||
private void testPasswordEncoding(Class<?> configClass, String providedPassword, String expectedPassword) {
|
||||
this.contextRunner.with(noOtherFormsOfAuthenticationOnTheClasspath())
|
||||
.withClassLoader(new FilteredClassLoader(ClientRegistrationRepository.class, OpaqueTokenIntrospector.class,
|
||||
RelyingPartyRegistrationRepository.class))
|
||||
.withUserConfiguration(configClass)
|
||||
this.contextRunner.withUserConfiguration(configClass)
|
||||
.withPropertyValues("spring.security.user.password=" + providedPassword)
|
||||
.run(((context) -> {
|
||||
InMemoryUserDetailsManager userDetailsService = context.getBean(InMemoryUserDetailsManager.class);
|
||||
@@ -211,6 +207,18 @@ class UserDetailsServiceAutoConfigurationTests {
|
||||
}));
|
||||
}
|
||||
|
||||
private ConditionOutcome outcomeOfMissingAlternativeCondition(ConfigurableApplicationContext context) {
|
||||
ConditionAndOutcomes conditionAndOutcomes = ConditionEvaluationReport.get(context.getBeanFactory())
|
||||
.getConditionAndOutcomesBySource()
|
||||
.get(UserDetailsServiceAutoConfiguration.class.getName());
|
||||
for (ConditionAndOutcome conditionAndOutcome : conditionAndOutcomes) {
|
||||
if (conditionAndOutcome.getCondition() instanceof MissingAlternativeOrUserPropertiesConfigured) {
|
||||
return conditionAndOutcome.getOutcome();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class TestAuthenticationManagerConfiguration {
|
||||
|
||||
@@ -306,4 +314,39 @@ class UserDetailsServiceAutoConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
private enum AlternativeFormOfAuthentication {
|
||||
|
||||
CLIENT_REGISTRATION_REPOSITORY(ClientRegistrationRepository.class),
|
||||
|
||||
OPAQUE_TOKEN_INTROSPECTOR(OpaqueTokenIntrospector.class),
|
||||
|
||||
RELYING_PARTY_REGISTRATION_REPOSITORY(RelyingPartyRegistrationRepository.class);
|
||||
|
||||
private final Class<?> type;
|
||||
|
||||
AlternativeFormOfAuthentication(Class<?> type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
private Class<?> getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
private Function<ApplicationContextRunner, ApplicationContextRunner> present() {
|
||||
return (contextRunner) -> contextRunner
|
||||
.withClassLoader(new FilteredClassLoader(Stream.of(AlternativeFormOfAuthentication.values())
|
||||
.filter(Predicate.not(this::equals))
|
||||
.map(AlternativeFormOfAuthentication::getType)
|
||||
.toArray(Class[]::new)));
|
||||
}
|
||||
|
||||
private static Function<ApplicationContextRunner, ApplicationContextRunner> nonPresent() {
|
||||
return (contextRunner) -> contextRunner
|
||||
.withClassLoader(new FilteredClassLoader(Stream.of(AlternativeFormOfAuthentication.values())
|
||||
.map(AlternativeFormOfAuthentication::getType)
|
||||
.toArray(Class[]::new)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user