Skip to content

Commit 1b93f84

Browse files
committed
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
1 parent 5e9cfea commit 1b93f84

File tree

2 files changed

+110
-7
lines changed

2 files changed

+110
-7
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/AuthenticationManagerConfiguration.java

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.boot.autoconfigure.security;
1818

1919
import java.util.List;
20+
import java.util.regex.Pattern;
2021

2122
import org.apache.commons.logging.Log;
2223
import org.apache.commons.logging.LogFactory;
@@ -26,13 +27,11 @@
2627
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
2728
import org.springframework.context.annotation.Bean;
2829
import org.springframework.context.annotation.Configuration;
29-
import org.springframework.core.annotation.Order;
3030
import org.springframework.security.authentication.AuthenticationManager;
3131
import org.springframework.security.authentication.AuthenticationProvider;
3232
import org.springframework.security.config.annotation.ObjectPostProcessor;
3333
import org.springframework.security.core.userdetails.User;
3434
import org.springframework.security.core.userdetails.UserDetailsService;
35-
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
3635
import org.springframework.security.crypto.password.PasswordEncoder;
3736
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
3837

@@ -50,26 +49,35 @@
5049
@ConditionalOnBean(ObjectPostProcessor.class)
5150
@ConditionalOnMissingBean({ AuthenticationManager.class, AuthenticationProvider.class,
5251
UserDetailsService.class })
53-
@Order(0)
5452
public class AuthenticationManagerConfiguration {
5553

54+
private final Pattern pattern = Pattern.compile("^\\{.+}.*$");
55+
5656
private static final Log logger = LogFactory
5757
.getLog(AuthenticationManagerConfiguration.class);
5858

59+
private static final String NOOP_PREFIX = "{noop}";
60+
5961
@Bean
6062
public InMemoryUserDetailsManager inMemoryUserDetailsManager(SecurityProperties properties,
6163
ObjectProvider<PasswordEncoder> passwordEncoder) throws Exception {
6264
SecurityProperties.User user = properties.getUser();
6365
if (user.isPasswordGenerated()) {
6466
logger.info(String.format("%n%nUsing generated security password: %s%n", user.getPassword()));
6567
}
66-
String encodedPassword = passwordEncoder
67-
.getIfAvailable(PasswordEncoderFactories::createDelegatingPasswordEncoder)
68-
.encode(user.getPassword());
68+
String password = deducePassword(passwordEncoder, user.getPassword());
6969
List<String> roles = user.getRoles();
7070
return new InMemoryUserDetailsManager(
71-
User.withUsername(user.getName()).password(encodedPassword)
71+
User.withUsername(user.getName()).password(password)
7272
.roles(roles.toArray(new String[roles.size()])).build());
7373
}
7474

75+
private String deducePassword(ObjectProvider<PasswordEncoder> passwordEncoder, String password) {
76+
if (passwordEncoder.getIfAvailable() == null &&
77+
!this.pattern.matcher(password).matches()) {
78+
return NOOP_PREFIX + password;
79+
}
80+
return password;
81+
}
82+
7583
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright 2012-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.security;
18+
19+
import org.junit.Test;
20+
21+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
22+
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
23+
import org.springframework.context.annotation.Bean;
24+
import org.springframework.context.annotation.Configuration;
25+
import org.springframework.context.annotation.Import;
26+
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
27+
import org.springframework.security.crypto.password.PasswordEncoder;
28+
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
29+
30+
import static org.assertj.core.api.Assertions.assertThat;
31+
import static org.mockito.Mockito.mock;
32+
33+
/**
34+
* Tests for {@link AuthenticationManagerConfiguration}.
35+
*
36+
* @author Madhura Bhave
37+
*/
38+
public class AuthenticationManagerConfigurationTests {
39+
40+
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner();
41+
42+
@Test
43+
public void userDetailsServiceWhenPasswordEncoderAbsentAndDefaultPassword() throws Exception {
44+
this.contextRunner.withUserConfiguration(TestSecurityConfiguration.class,
45+
AuthenticationManagerConfiguration.class).run((context -> {
46+
InMemoryUserDetailsManager userDetailsService = context.getBean(InMemoryUserDetailsManager.class);
47+
String password = userDetailsService.loadUserByUsername("user").getPassword();
48+
assertThat(password).startsWith("{noop}");
49+
}));
50+
}
51+
52+
@Test
53+
public void userDetailsServiceWhenPasswordEncoderAbsentAndRawPassword() throws Exception {
54+
testPasswordEncoding(TestSecurityConfiguration.class, "secret", "{noop}secret");
55+
}
56+
57+
@Test
58+
public void userDetailsServiceWhenPasswordEncoderAbsentAndEncodedPassword() throws Exception {
59+
String password = "{bcrypt}$2a$10$sCBi9fy9814vUPf2ZRbtp.fR5/VgRk2iBFZ.ypu5IyZ28bZgxrVDa";
60+
testPasswordEncoding(TestSecurityConfiguration.class, password, password);
61+
}
62+
63+
@Test
64+
public void userDetailsServiceWhenPasswordEncoderBeanPresent() throws Exception {
65+
testPasswordEncoding(TestConfigWithPasswordEncoder.class, "secret", "secret");
66+
}
67+
68+
private void testPasswordEncoding(Class<?> configClass, String providedPassword, String expectedPassword) {
69+
this.contextRunner.withUserConfiguration(configClass,
70+
AuthenticationManagerConfiguration.class)
71+
.withPropertyValues("spring.security.user.password=" + providedPassword).run((context -> {
72+
InMemoryUserDetailsManager userDetailsService = context.getBean(InMemoryUserDetailsManager.class);
73+
String password = userDetailsService.loadUserByUsername("user").getPassword();
74+
assertThat(password).isEqualTo(expectedPassword);
75+
}));
76+
}
77+
78+
@Configuration
79+
@EnableWebSecurity
80+
@EnableConfigurationProperties(SecurityProperties.class)
81+
protected static class TestSecurityConfiguration {
82+
83+
}
84+
85+
@Configuration
86+
@Import(TestSecurityConfiguration.class)
87+
protected static class TestConfigWithPasswordEncoder {
88+
89+
@Bean
90+
public PasswordEncoder passwordEncoder() {
91+
return mock(PasswordEncoder.class);
92+
}
93+
94+
}
95+
}

0 commit comments

Comments
 (0)