Make user details only back off without custom username or password

Closes gh-38864
This commit is contained in:
Andy Wilkinson 2024-01-18 14:14:39 +00:00
parent 0f53415451
commit 961da4e428
4 changed files with 96 additions and 12 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2023 the original author or authors. * Copyright 2012-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -29,6 +29,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; 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;
import org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration; import org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration;
import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.boot.autoconfigure.security.SecurityProperties;
@ -58,13 +59,12 @@ import org.springframework.util.StringUtils;
*/ */
@AutoConfiguration(before = ReactiveSecurityAutoConfiguration.class, after = RSocketMessagingAutoConfiguration.class) @AutoConfiguration(before = ReactiveSecurityAutoConfiguration.class, after = RSocketMessagingAutoConfiguration.class)
@ConditionalOnClass({ ReactiveAuthenticationManager.class }) @ConditionalOnClass({ ReactiveAuthenticationManager.class })
@ConditionalOnMissingClass({ "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository",
"org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector" })
@ConditionalOnMissingBean( @ConditionalOnMissingBean(
value = { ReactiveAuthenticationManager.class, ReactiveUserDetailsService.class, value = { ReactiveAuthenticationManager.class, ReactiveUserDetailsService.class,
ReactiveAuthenticationManagerResolver.class }, ReactiveAuthenticationManagerResolver.class },
type = { "org.springframework.security.oauth2.jwt.ReactiveJwtDecoder" }) type = { "org.springframework.security.oauth2.jwt.ReactiveJwtDecoder" })
@Conditional(ReactiveUserDetailsServiceAutoConfiguration.ReactiveUserDetailsServiceCondition.class) @Conditional({ ReactiveUserDetailsServiceAutoConfiguration.RSocketEnabledOrReactiveWebApplication.class,
ReactiveUserDetailsServiceAutoConfiguration.MissingAlternativeOrUserPropertiesConfigured.class })
@EnableConfigurationProperties(SecurityProperties.class) @EnableConfigurationProperties(SecurityProperties.class)
public class ReactiveUserDetailsServiceAutoConfiguration { public class ReactiveUserDetailsServiceAutoConfiguration {
@ -98,9 +98,9 @@ public class ReactiveUserDetailsServiceAutoConfiguration {
return NOOP_PASSWORD_PREFIX + password; return NOOP_PASSWORD_PREFIX + password;
} }
static class ReactiveUserDetailsServiceCondition extends AnyNestedCondition { static class RSocketEnabledOrReactiveWebApplication extends AnyNestedCondition {
ReactiveUserDetailsServiceCondition() { RSocketEnabledOrReactiveWebApplication() {
super(ConfigurationPhase.REGISTER_BEAN); super(ConfigurationPhase.REGISTER_BEAN);
} }
@ -116,4 +116,29 @@ public class ReactiveUserDetailsServiceAutoConfiguration {
} }
static final class MissingAlternativeOrUserPropertiesConfigured extends AnyNestedCondition {
MissingAlternativeOrUserPropertiesConfigured() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
@ConditionalOnMissingClass({
"org.springframework.security.oauth2.client.registration.ClientRegistrationRepository",
"org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector" })
static final class MissingAlternative {
}
@ConditionalOnProperty(prefix = "spring.security.user", name = "name")
static final class NameConfigured {
}
@ConditionalOnProperty(prefix = "spring.security.user", name = "password")
static final class PasswordConfigured {
}
}
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2023 the original author or authors. * Copyright 2012-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -25,12 +25,16 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration.MissingAlternativeOrUserPropertiesConfigured;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationManagerResolver; import org.springframework.security.authentication.AuthenticationManagerResolver;
import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationProvider;
@ -53,9 +57,7 @@ import org.springframework.util.StringUtils;
*/ */
@AutoConfiguration @AutoConfiguration
@ConditionalOnClass(AuthenticationManager.class) @ConditionalOnClass(AuthenticationManager.class)
@ConditionalOnMissingClass({ "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository", @Conditional(MissingAlternativeOrUserPropertiesConfigured.class)
"org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector",
"org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository" })
@ConditionalOnBean(ObjectPostProcessor.class) @ConditionalOnBean(ObjectPostProcessor.class)
@ConditionalOnMissingBean(value = { AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class, @ConditionalOnMissingBean(value = { AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class,
AuthenticationManagerResolver.class }, type = "org.springframework.security.oauth2.jwt.JwtDecoder") AuthenticationManagerResolver.class }, type = "org.springframework.security.oauth2.jwt.JwtDecoder")
@ -93,4 +95,30 @@ public class UserDetailsServiceAutoConfiguration {
return NOOP_PASSWORD_PREFIX + password; return NOOP_PASSWORD_PREFIX + password;
} }
static final class MissingAlternativeOrUserPropertiesConfigured extends AnyNestedCondition {
MissingAlternativeOrUserPropertiesConfigured() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
@ConditionalOnMissingClass({
"org.springframework.security.oauth2.client.registration.ClientRegistrationRepository",
"org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector",
"org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository" })
static final class MissingAlternative {
}
@ConditionalOnProperty(prefix = "spring.security.user", name = "name")
static final class NameConfigured {
}
@ConditionalOnProperty(prefix = "spring.security.user", name = "password")
static final class PasswordConfigured {
}
}
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2023 the original author or authors. * Copyright 2012-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -121,6 +121,18 @@ class ReactiveUserDetailsServiceAutoConfigurationTests {
this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(ReactiveUserDetailsService.class)); this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(ReactiveUserDetailsService.class));
} }
@Test
void configuresDefaultUserWhenResourceServerIsPresentAndUsernameIsConfigured() {
this.contextRunner.withPropertyValues("spring.security.user.name=carol")
.run((context) -> assertThat(context).hasSingleBean(ReactiveUserDetailsService.class));
}
@Test
void configuresDefaultUserWhenResourceServerIsPresentAndPasswordIsConfigured() {
this.contextRunner.withPropertyValues("spring.security.user.password=p4ssw0rd")
.run((context) -> assertThat(context).hasSingleBean(ReactiveUserDetailsService.class));
}
@Test @Test
void userDetailsServiceWhenPasswordEncoderAbsentAndDefaultPassword() { void userDetailsServiceWhenPasswordEncoderAbsentAndDefaultPassword() {
this.contextRunner this.contextRunner

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2023 the original author or authors. * Copyright 2012-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -23,8 +23,10 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener;
import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.system.CapturedOutput; import org.springframework.boot.test.system.CapturedOutput;
@ -176,6 +178,23 @@ class UserDetailsServiceAutoConfigurationTests {
.run(((context) -> assertThat(context).doesNotHaveBean(InMemoryUserDetailsManager.class))); .run(((context) -> assertThat(context).doesNotHaveBean(InMemoryUserDetailsManager.class)));
} }
@Test
void userDetailsServiceWhenRelyingPartyRegistrationRepositoryPresentAndUsernameConfigured() {
this.contextRunner
.withClassLoader(new FilteredClassLoader(ClientRegistrationRepository.class, OpaqueTokenIntrospector.class))
.withPropertyValues("spring.security.user.name=alice")
.withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO))
.run(((context) -> assertThat(context).hasSingleBean(InMemoryUserDetailsManager.class)));
}
@Test
void userDetailsServiceWhenRelyingPartyRegistrationRepositoryPresentAndPasswordConfigured() {
this.contextRunner
.withClassLoader(new FilteredClassLoader(ClientRegistrationRepository.class, OpaqueTokenIntrospector.class))
.withPropertyValues("spring.security.user.password=secret")
.run(((context) -> assertThat(context).hasSingleBean(InMemoryUserDetailsManager.class)));
}
private Function<ApplicationContextRunner, ApplicationContextRunner> noOtherFormsOfAuthenticationOnTheClasspath() { private Function<ApplicationContextRunner, ApplicationContextRunner> noOtherFormsOfAuthenticationOnTheClasspath() {
return (contextRunner) -> contextRunner return (contextRunner) -> contextRunner
.withClassLoader(new FilteredClassLoader(ClientRegistrationRepository.class, OpaqueTokenIntrospector.class, .withClassLoader(new FilteredClassLoader(ClientRegistrationRepository.class, OpaqueTokenIntrospector.class,