Allow encoding default password in reactive user details

See gh-10963
This commit is contained in:
Madhura Bhave 2017-12-20 10:29:20 -08:00
parent 1b93f84912
commit ec26488ff1
2 changed files with 114 additions and 7 deletions

View File

@ -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> 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> passwordEncoder) {
String encodedPassword = passwordEncoder
.getIfAvailable(PasswordEncoderFactories::createDelegatingPasswordEncoder)
.encode(user.getPassword());
String password) {
List<String> 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();
}

View File

@ -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);
}
}
}