Skip to content

Commit ec26488

Browse files
committed
Allow encoding default password in reactive user details
See gh-10963
1 parent 1b93f84 commit ec26488

File tree

2 files changed

+114
-7
lines changed

2 files changed

+114
-7
lines changed

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

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.boot.autoconfigure.security.reactive;
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;
@@ -33,7 +34,6 @@
3334
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
3435
import org.springframework.security.core.userdetails.User;
3536
import org.springframework.security.core.userdetails.UserDetails;
36-
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
3737
import org.springframework.security.crypto.password.PasswordEncoder;
3838

3939
/**
@@ -51,6 +51,10 @@
5151
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
5252
class ReactiveAuthenticationManagerConfiguration {
5353

54+
private final Pattern pattern = Pattern.compile("^\\{.+}.*$");
55+
56+
private static final String NOOP_PREFIX = "{noop}";
57+
5458
private static final Log logger = LogFactory
5559
.getLog(ReactiveAuthenticationManagerConfiguration.class);
5660

@@ -63,17 +67,23 @@ public MapReactiveUserDetailsService reactiveUserDetailsService(
6367
logger.info(String.format("%n%nUsing default security password: %s%n",
6468
user.getPassword()));
6569
}
66-
UserDetails userDetails = getUserDetails(user, passwordEncoder);
70+
String password = deducePassword(passwordEncoder, user.getPassword());
71+
UserDetails userDetails = getUserDetails(user, password);
6772
return new MapReactiveUserDetailsService(userDetails);
6873
}
6974

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+
7083
private UserDetails getUserDetails(SecurityProperties.User user,
71-
ObjectProvider<PasswordEncoder> passwordEncoder) {
72-
String encodedPassword = passwordEncoder
73-
.getIfAvailable(PasswordEncoderFactories::createDelegatingPasswordEncoder)
74-
.encode(user.getPassword());
84+
String password) {
7585
List<String> roles = user.getRoles();
76-
return User.withUsername(user.getName()).password(encodedPassword)
86+
return User.withUsername(user.getName()).password(password)
7787
.roles(roles.toArray(new String[roles.size()])).build();
7888
}
7989

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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.reactive;
18+
19+
import org.junit.Test;
20+
21+
import org.springframework.boot.autoconfigure.security.SecurityProperties;
22+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
23+
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
24+
import org.springframework.context.annotation.Bean;
25+
import org.springframework.context.annotation.Configuration;
26+
import org.springframework.context.annotation.Import;
27+
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
28+
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
29+
import org.springframework.security.crypto.password.PasswordEncoder;
30+
31+
import static org.assertj.core.api.Assertions.assertThat;
32+
import static org.mockito.Mockito.mock;
33+
34+
/**
35+
* Tests for {@link ReactiveAuthenticationManagerConfiguration}.
36+
*
37+
* @author Madhura Bhave
38+
*/
39+
public class ReactiveAuthenticationManagerConfigurationTests {
40+
41+
private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner();
42+
43+
@Test
44+
public void userDetailsServiceWhenPasswordEncoderAbsentAndDefaultPassword() throws Exception {
45+
this.contextRunner.withUserConfiguration(TestSecurityConfiguration.class,
46+
ReactiveAuthenticationManagerConfiguration.class).run((context -> {
47+
MapReactiveUserDetailsService userDetailsService = context.getBean(MapReactiveUserDetailsService.class);
48+
String password = userDetailsService.findByUsername("user").block().getPassword();
49+
assertThat(password).startsWith("{noop}");
50+
}));
51+
}
52+
53+
@Test
54+
public void userDetailsServiceWhenPasswordEncoderAbsentAndRawPassword() throws Exception {
55+
testPasswordEncoding(TestSecurityConfiguration.class, "secret", "{noop}secret");
56+
}
57+
58+
@Test
59+
public void userDetailsServiceWhenPasswordEncoderAbsentAndEncodedPassword() throws Exception {
60+
String password = "{bcrypt}$2a$10$sCBi9fy9814vUPf2ZRbtp.fR5/VgRk2iBFZ.ypu5IyZ28bZgxrVDa";
61+
testPasswordEncoding(TestSecurityConfiguration.class, password, password);
62+
}
63+
64+
@Test
65+
public void userDetailsServiceWhenPasswordEncoderBeanPresent() throws Exception {
66+
testPasswordEncoding(TestConfigWithPasswordEncoder.class, "secret", "secret");
67+
}
68+
69+
private void testPasswordEncoding(Class<?> configClass, String providedPassword, String expectedPassword) {
70+
this.contextRunner.withUserConfiguration(configClass,
71+
ReactiveAuthenticationManagerConfiguration.class)
72+
.withPropertyValues("spring.security.user.password=" + providedPassword).run((context -> {
73+
MapReactiveUserDetailsService userDetailsService = context.getBean(MapReactiveUserDetailsService.class);
74+
String password = userDetailsService.findByUsername("user").block().getPassword();
75+
assertThat(password).isEqualTo(expectedPassword);
76+
}));
77+
}
78+
79+
@Configuration
80+
@EnableWebFluxSecurity
81+
@EnableConfigurationProperties(SecurityProperties.class)
82+
protected static class TestSecurityConfiguration {
83+
84+
}
85+
86+
@Configuration
87+
@Import(TestSecurityConfiguration.class)
88+
protected static class TestConfigWithPasswordEncoder {
89+
90+
@Bean
91+
public PasswordEncoder passwordEncoder() {
92+
return mock(PasswordEncoder.class);
93+
}
94+
95+
}
96+
97+
}

0 commit comments

Comments
 (0)