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");
* 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.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.rsocket.RSocketMessagingAutoConfiguration;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
@ -58,13 +59,12 @@ import org.springframework.util.StringUtils;
*/
@AutoConfiguration(before = ReactiveSecurityAutoConfiguration.class, after = RSocketMessagingAutoConfiguration.class)
@ConditionalOnClass({ ReactiveAuthenticationManager.class })
@ConditionalOnMissingClass({ "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository",
"org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector" })
@ConditionalOnMissingBean(
value = { ReactiveAuthenticationManager.class, ReactiveUserDetailsService.class,
ReactiveAuthenticationManagerResolver.class },
type = { "org.springframework.security.oauth2.jwt.ReactiveJwtDecoder" })
@Conditional(ReactiveUserDetailsServiceAutoConfiguration.ReactiveUserDetailsServiceCondition.class)
@Conditional({ ReactiveUserDetailsServiceAutoConfiguration.RSocketEnabledOrReactiveWebApplication.class,
ReactiveUserDetailsServiceAutoConfiguration.MissingAlternativeOrUserPropertiesConfigured.class })
@EnableConfigurationProperties(SecurityProperties.class)
public class ReactiveUserDetailsServiceAutoConfiguration {
@ -98,9 +98,9 @@ public class ReactiveUserDetailsServiceAutoConfiguration {
return NOOP_PASSWORD_PREFIX + password;
}
static class ReactiveUserDetailsServiceCondition extends AnyNestedCondition {
static class RSocketEnabledOrReactiveWebApplication extends AnyNestedCondition {
ReactiveUserDetailsServiceCondition() {
RSocketEnabledOrReactiveWebApplication() {
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");
* 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.boot.autoconfigure.AutoConfiguration;
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.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.security.SecurityProperties;
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration.MissingAlternativeOrUserPropertiesConfigured;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationManagerResolver;
import org.springframework.security.authentication.AuthenticationProvider;
@ -53,9 +57,7 @@ import org.springframework.util.StringUtils;
*/
@AutoConfiguration
@ConditionalOnClass(AuthenticationManager.class)
@ConditionalOnMissingClass({ "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository",
"org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector",
"org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository" })
@Conditional(MissingAlternativeOrUserPropertiesConfigured.class)
@ConditionalOnBean(ObjectPostProcessor.class)
@ConditionalOnMissingBean(value = { AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class,
AuthenticationManagerResolver.class }, type = "org.springframework.security.oauth2.jwt.JwtDecoder")
@ -93,4 +95,30 @@ public class UserDetailsServiceAutoConfiguration {
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");
* 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));
}
@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
void userDetailsServiceWhenPasswordEncoderAbsentAndDefaultPassword() {
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");
* 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.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
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.runner.ApplicationContextRunner;
import org.springframework.boot.test.system.CapturedOutput;
@ -176,6 +178,23 @@ class UserDetailsServiceAutoConfigurationTests {
.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() {
return (contextRunner) -> contextRunner
.withClassLoader(new FilteredClassLoader(ClientRegistrationRepository.class, OpaqueTokenIntrospector.class,