Merge pull request #43334 from BenchmarkingBuffalo

* pr/43334:
  Polish "Make UserDetailsServiceAutoConfiguration conditional on servlet app"
  Make UserDetailsServiceAutoConfiguration conditional on servlet app

Closes gh-43334
This commit is contained in:
Moritz Halbritter 2024-12-02 13:41:04 +01:00
commit 32b39955c6
2 changed files with 54 additions and 10 deletions

View File

@ -31,6 +31,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration.MissingAlternativeOrUserPropertiesConfigured;
import org.springframework.context.annotation.Bean;
@ -53,6 +55,7 @@ import org.springframework.util.StringUtils;
* @author Dave Syer
* @author Rob Winch
* @author Madhura Bhave
* @author Lasse Wulff
* @since 2.0.0
*/
@AutoConfiguration
@ -61,6 +64,7 @@ import org.springframework.util.StringUtils;
@ConditionalOnBean(ObjectPostProcessor.class)
@ConditionalOnMissingBean(value = { AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class,
AuthenticationManagerResolver.class }, type = "org.springframework.security.oauth2.jwt.JwtDecoder")
@ConditionalOnWebApplication(type = Type.SERVLET)
public class UserDetailsServiceAutoConfiguration {
private static final String NOOP_PASSWORD_PREFIX = "{noop}";

View File

@ -27,6 +27,8 @@ import org.springframework.boot.autoconfigure.security.SecurityProperties;
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.context.runner.ReactiveWebApplicationContextRunner;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension;
import org.springframework.context.annotation.Bean;
@ -56,17 +58,41 @@ import static org.mockito.Mockito.mock;
*
* @author Madhura Bhave
* @author HaiTao Zhang
* @author Lasse Wulff
* @author Moritz Halbritter
*/
@ExtendWith(OutputCaptureExtension.class)
class UserDetailsServiceAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
.withUserConfiguration(TestSecurityConfiguration.class)
.withConfiguration(AutoConfigurations.of(UserDetailsServiceAutoConfiguration.class));
@Test
void shouldSupplyUserDetailsServiceInServletApp() {
this.contextRunner.with(AuthenticationExclude.servletApp())
.run((context) -> assertThat(context).hasSingleBean(UserDetailsService.class));
}
@Test
void shouldNotSupplyUserDetailsServiceInReactiveApp() {
new ReactiveWebApplicationContextRunner().withUserConfiguration(TestSecurityConfiguration.class)
.withConfiguration(AutoConfigurations.of(UserDetailsServiceAutoConfiguration.class))
.with(AuthenticationExclude.reactiveApp())
.run((context) -> assertThat(context).doesNotHaveBean(UserDetailsService.class));
}
@Test
void shouldNotSupplyUserDetailsServiceInNonWebApp() {
new ApplicationContextRunner().withUserConfiguration(TestSecurityConfiguration.class)
.withConfiguration(AutoConfigurations.of(UserDetailsServiceAutoConfiguration.class))
.with(AuthenticationExclude.noWebApp())
.run((context) -> assertThat(context).doesNotHaveBean(UserDetailsService.class));
}
@Test
void testDefaultUsernamePassword(CapturedOutput output) {
this.contextRunner.with(noOtherFormsOfAuthenticationOnTheClasspath()).run((context) -> {
this.contextRunner.with(AuthenticationExclude.servletApp()).run((context) -> {
UserDetailsService manager = context.getBean(UserDetailsService.class);
assertThat(output).contains("Using generated security password:");
assertThat(manager.loadUserByUsername("user")).isNotNull();
@ -128,7 +154,7 @@ class UserDetailsServiceAutoConfigurationTests {
@Test
void userDetailsServiceWhenPasswordEncoderAbsentAndDefaultPassword() {
this.contextRunner.with(noOtherFormsOfAuthenticationOnTheClasspath())
this.contextRunner.with(AuthenticationExclude.servletApp())
.withUserConfiguration(TestSecurityConfiguration.class)
.run(((context) -> {
InMemoryUserDetailsManager userDetailsService = context.getBean(InMemoryUserDetailsManager.class);
@ -192,14 +218,8 @@ class UserDetailsServiceAutoConfigurationTests {
.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())
this.contextRunner.with(AuthenticationExclude.servletApp())
.withClassLoader(new FilteredClassLoader(ClientRegistrationRepository.class, OpaqueTokenIntrospector.class,
RelyingPartyRegistrationRepository.class))
.withUserConfiguration(configClass)
@ -211,6 +231,26 @@ class UserDetailsServiceAutoConfigurationTests {
}));
}
private static final class AuthenticationExclude {
private static final FilteredClassLoader filteredClassLoader = new FilteredClassLoader(
ClientRegistrationRepository.class, OpaqueTokenIntrospector.class,
RelyingPartyRegistrationRepository.class);
static Function<WebApplicationContextRunner, WebApplicationContextRunner> servletApp() {
return (contextRunner) -> contextRunner.withClassLoader(filteredClassLoader);
}
static Function<ReactiveWebApplicationContextRunner, ReactiveWebApplicationContextRunner> reactiveApp() {
return (contextRunner) -> contextRunner.withClassLoader(filteredClassLoader);
}
static Function<ApplicationContextRunner, ApplicationContextRunner> noWebApp() {
return (contextRunner) -> contextRunner.withClassLoader(filteredClassLoader);
}
}
@Configuration(proxyBeanMethods = false)
static class TestAuthenticationManagerConfiguration {