diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveAuthenticationManagerConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveAuthenticationManagerConfiguration.java index 9f3b968d233..2acded6c1d7 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveAuthenticationManagerConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveAuthenticationManagerConfiguration.java @@ -17,6 +17,7 @@ package org.springframework.boot.autoconfigure.security.reactive; import java.util.List; +import java.util.regex.Pattern; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -33,7 +34,6 @@ import org.springframework.security.core.userdetails.MapReactiveUserDetailsServi import org.springframework.security.core.userdetails.ReactiveUserDetailsService; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; /** @@ -51,6 +51,10 @@ import org.springframework.security.crypto.password.PasswordEncoder; @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) class ReactiveAuthenticationManagerConfiguration { + private final Pattern pattern = Pattern.compile("^\\{.+}.*$"); + + private static final String NOOP_PREFIX = "{noop}"; + private static final Log logger = LogFactory .getLog(ReactiveAuthenticationManagerConfiguration.class); @@ -63,17 +67,23 @@ class ReactiveAuthenticationManagerConfiguration { logger.info(String.format("%n%nUsing default security password: %s%n", user.getPassword())); } - UserDetails userDetails = getUserDetails(user, passwordEncoder); + String password = deducePassword(passwordEncoder, user.getPassword()); + UserDetails userDetails = getUserDetails(user, password); return new MapReactiveUserDetailsService(userDetails); } + private String deducePassword(ObjectProvider passwordEncoder, String password) { + if (passwordEncoder.getIfAvailable() == null && + !this.pattern.matcher(password).matches()) { + return NOOP_PREFIX + password; + } + return password; + } + private UserDetails getUserDetails(SecurityProperties.User user, - ObjectProvider passwordEncoder) { - String encodedPassword = passwordEncoder - .getIfAvailable(PasswordEncoderFactories::createDelegatingPasswordEncoder) - .encode(user.getPassword()); + String password) { List roles = user.getRoles(); - return User.withUsername(user.getName()).password(encodedPassword) + return User.withUsername(user.getName()).password(password) .roles(roles.toArray(new String[roles.size()])).build(); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveAuthenticationManagerConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveAuthenticationManagerConfigurationTests.java new file mode 100644 index 00000000000..bb62585e627 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveAuthenticationManagerConfigurationTests.java @@ -0,0 +1,97 @@ +/* + * 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.reactive; + +import org.junit.Test; + +import org.springframework.boot.autoconfigure.security.SecurityProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; +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.reactive.EnableWebFluxSecurity; +import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; +import org.springframework.security.crypto.password.PasswordEncoder; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link ReactiveAuthenticationManagerConfiguration}. + * + * @author Madhura Bhave + */ +public class ReactiveAuthenticationManagerConfigurationTests { + + private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner(); + + @Test + public void userDetailsServiceWhenPasswordEncoderAbsentAndDefaultPassword() throws Exception { + this.contextRunner.withUserConfiguration(TestSecurityConfiguration.class, + ReactiveAuthenticationManagerConfiguration.class).run((context -> { + MapReactiveUserDetailsService userDetailsService = context.getBean(MapReactiveUserDetailsService.class); + String password = userDetailsService.findByUsername("user").block().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, + ReactiveAuthenticationManagerConfiguration.class) + .withPropertyValues("spring.security.user.password=" + providedPassword).run((context -> { + MapReactiveUserDetailsService userDetailsService = context.getBean(MapReactiveUserDetailsService.class); + String password = userDetailsService.findByUsername("user").block().getPassword(); + assertThat(password).isEqualTo(expectedPassword); + })); + } + + @Configuration + @EnableWebFluxSecurity + @EnableConfigurationProperties(SecurityProperties.class) + protected static class TestSecurityConfiguration { + + } + + @Configuration + @Import(TestSecurityConfiguration.class) + protected static class TestConfigWithPasswordEncoder { + + @Bean + public PasswordEncoder passwordEncoder() { + return mock(PasswordEncoder.class); + } + + } + +}