commit
67cb738337
|
|
@ -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,19 +18,30 @@ 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.AbstractApplicationContextRunner;
|
||||
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.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
|
@ -70,7 +81,7 @@ class UserDetailsServiceAutoConfigurationTests {
|
|||
|
||||
@Test
|
||||
void shouldSupplyUserDetailsServiceInServletApp() {
|
||||
this.contextRunner.with(AuthenticationExclude.servletApp())
|
||||
this.contextRunner.with(AlternativeFormOfAuthentication.nonPresent())
|
||||
.run((context) -> assertThat(context).hasSingleBean(UserDetailsService.class));
|
||||
}
|
||||
|
||||
|
|
@ -78,7 +89,7 @@ class UserDetailsServiceAutoConfigurationTests {
|
|||
void shouldNotSupplyUserDetailsServiceInReactiveApp() {
|
||||
new ReactiveWebApplicationContextRunner().withUserConfiguration(TestSecurityConfiguration.class)
|
||||
.withConfiguration(AutoConfigurations.of(UserDetailsServiceAutoConfiguration.class))
|
||||
.with(AuthenticationExclude.reactiveApp())
|
||||
.with(AlternativeFormOfAuthentication.nonPresent())
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(UserDetailsService.class));
|
||||
}
|
||||
|
||||
|
|
@ -86,13 +97,14 @@ class UserDetailsServiceAutoConfigurationTests {
|
|||
void shouldNotSupplyUserDetailsServiceInNonWebApp() {
|
||||
new ApplicationContextRunner().withUserConfiguration(TestSecurityConfiguration.class)
|
||||
.withConfiguration(AutoConfigurations.of(UserDetailsServiceAutoConfiguration.class))
|
||||
.with(AuthenticationExclude.noWebApp())
|
||||
.with(AlternativeFormOfAuthentication.nonPresent())
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(UserDetailsService.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDefaultUsernamePassword(CapturedOutput output) {
|
||||
this.contextRunner.with(AuthenticationExclude.servletApp()).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();
|
||||
|
|
@ -101,60 +113,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(AuthenticationExclude.servletApp())
|
||||
this.contextRunner.with(AlternativeFormOfAuthentication.nonPresent())
|
||||
.withUserConfiguration(TestSecurityConfiguration.class)
|
||||
.run(((context) -> {
|
||||
InMemoryUserDetailsManager userDetailsService = context.getBean(InMemoryUserDetailsManager.class);
|
||||
|
|
@ -179,49 +199,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 void testPasswordEncoding(Class<?> configClass, String providedPassword, String expectedPassword) {
|
||||
this.contextRunner.with(AuthenticationExclude.servletApp())
|
||||
.withClassLoader(new FilteredClassLoader(ClientRegistrationRepository.class, OpaqueTokenIntrospector.class,
|
||||
RelyingPartyRegistrationRepository.class))
|
||||
this.contextRunner.with(AlternativeFormOfAuthentication.nonPresent())
|
||||
.withUserConfiguration(configClass)
|
||||
.withPropertyValues("spring.security.user.password=" + providedPassword)
|
||||
.run(((context) -> {
|
||||
|
|
@ -231,24 +235,16 @@ 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);
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
static Function<ReactiveWebApplicationContextRunner, ReactiveWebApplicationContextRunner> reactiveApp() {
|
||||
return (contextRunner) -> contextRunner.withClassLoader(filteredClassLoader);
|
||||
}
|
||||
|
||||
static Function<ApplicationContextRunner, ApplicationContextRunner> noWebApp() {
|
||||
return (contextRunner) -> contextRunner.withClassLoader(filteredClassLoader);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
|
|
@ -346,4 +342,41 @@ 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;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T extends AbstractApplicationContextRunner<?, ?, ?>> Function<T, T> present() {
|
||||
return (contextRunner) -> (T) contextRunner
|
||||
.withClassLoader(new FilteredClassLoader(Stream.of(AlternativeFormOfAuthentication.values())
|
||||
.filter(Predicate.not(this::equals))
|
||||
.map(AlternativeFormOfAuthentication::getType)
|
||||
.toArray(Class[]::new)));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T extends AbstractApplicationContextRunner<?, ?, ?>> Function<T, T> nonPresent() {
|
||||
return (contextRunner) -> (T) contextRunner
|
||||
.withClassLoader(new FilteredClassLoader(Stream.of(AlternativeFormOfAuthentication.values())
|
||||
.map(AlternativeFormOfAuthentication::getType)
|
||||
.toArray(Class[]::new)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue