From 1b93f849129a22f7223e9da0a5daf7e3c5d65c4e Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Mon, 18 Dec 2017 14:16:46 -0800 Subject: [PATCH] Allow encoded password for default user If raw password is provided, add {noop} prefix. If prefix is present or PasswordEncoder bean is provided, use the password as is. Closes gh-10963 --- .../AuthenticationManagerConfiguration.java | 22 +++-- ...thenticationManagerConfigurationTests.java | 95 +++++++++++++++++++ 2 files changed, 110 insertions(+), 7 deletions(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/AuthenticationManagerConfigurationTests.java diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/AuthenticationManagerConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/AuthenticationManagerConfiguration.java index c9f22d59da..02ca7e2ccf 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/AuthenticationManagerConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/AuthenticationManagerConfiguration.java @@ -17,6 +17,7 @@ package org.springframework.boot.autoconfigure.security; import java.util.List; +import java.util.regex.Pattern; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -26,13 +27,11 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.annotation.Order; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; @@ -50,12 +49,15 @@ import org.springframework.security.provisioning.InMemoryUserDetailsManager; @ConditionalOnBean(ObjectPostProcessor.class) @ConditionalOnMissingBean({ AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class }) -@Order(0) public class AuthenticationManagerConfiguration { + private final Pattern pattern = Pattern.compile("^\\{.+}.*$"); + private static final Log logger = LogFactory .getLog(AuthenticationManagerConfiguration.class); + private static final String NOOP_PREFIX = "{noop}"; + @Bean public InMemoryUserDetailsManager inMemoryUserDetailsManager(SecurityProperties properties, ObjectProvider passwordEncoder) throws Exception { @@ -63,13 +65,19 @@ public class AuthenticationManagerConfiguration { if (user.isPasswordGenerated()) { logger.info(String.format("%n%nUsing generated security password: %s%n", user.getPassword())); } - String encodedPassword = passwordEncoder - .getIfAvailable(PasswordEncoderFactories::createDelegatingPasswordEncoder) - .encode(user.getPassword()); + String password = deducePassword(passwordEncoder, user.getPassword()); List roles = user.getRoles(); return new InMemoryUserDetailsManager( - User.withUsername(user.getName()).password(encodedPassword) + User.withUsername(user.getName()).password(password) .roles(roles.toArray(new String[roles.size()])).build()); } + private String deducePassword(ObjectProvider passwordEncoder, String password) { + if (passwordEncoder.getIfAvailable() == null && + !this.pattern.matcher(password).matches()) { + return NOOP_PREFIX + password; + } + return password; + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/AuthenticationManagerConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/AuthenticationManagerConfigurationTests.java new file mode 100644 index 0000000000..e9b368ad05 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/AuthenticationManagerConfigurationTests.java @@ -0,0 +1,95 @@ +/* + * Copyright 2012-2017 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.security; + +import org.junit.Test; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link AuthenticationManagerConfiguration}. + * + * @author Madhura Bhave + */ +public class AuthenticationManagerConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); + + @Test + public void userDetailsServiceWhenPasswordEncoderAbsentAndDefaultPassword() throws Exception { + this.contextRunner.withUserConfiguration(TestSecurityConfiguration.class, + AuthenticationManagerConfiguration.class).run((context -> { + InMemoryUserDetailsManager userDetailsService = context.getBean(InMemoryUserDetailsManager.class); + String password = userDetailsService.loadUserByUsername("user").getPassword(); + assertThat(password).startsWith("{noop}"); + })); + } + + @Test + public void userDetailsServiceWhenPasswordEncoderAbsentAndRawPassword() throws Exception { + testPasswordEncoding(TestSecurityConfiguration.class, "secret", "{noop}secret"); + } + + @Test + public void userDetailsServiceWhenPasswordEncoderAbsentAndEncodedPassword() throws Exception { + String password = "{bcrypt}$2a$10$sCBi9fy9814vUPf2ZRbtp.fR5/VgRk2iBFZ.ypu5IyZ28bZgxrVDa"; + testPasswordEncoding(TestSecurityConfiguration.class, password, password); + } + + @Test + public void userDetailsServiceWhenPasswordEncoderBeanPresent() throws Exception { + testPasswordEncoding(TestConfigWithPasswordEncoder.class, "secret", "secret"); + } + + private void testPasswordEncoding(Class configClass, String providedPassword, String expectedPassword) { + this.contextRunner.withUserConfiguration(configClass, + AuthenticationManagerConfiguration.class) + .withPropertyValues("spring.security.user.password=" + providedPassword).run((context -> { + InMemoryUserDetailsManager userDetailsService = context.getBean(InMemoryUserDetailsManager.class); + String password = userDetailsService.loadUserByUsername("user").getPassword(); + assertThat(password).isEqualTo(expectedPassword); + })); + } + + @Configuration + @EnableWebSecurity + @EnableConfigurationProperties(SecurityProperties.class) + protected static class TestSecurityConfiguration { + + } + + @Configuration + @Import(TestSecurityConfiguration.class) + protected static class TestConfigWithPasswordEncoder { + + @Bean + public PasswordEncoder passwordEncoder() { + return mock(PasswordEncoder.class); + } + + } +}