Skip to content

Commit 36382e5

Browse files
committed
Add support for Redis 6 authentication with username
Closes gh-22702
1 parent a5c20a5 commit 36382e5

File tree

4 files changed

+79
-25
lines changed

4 files changed

+79
-25
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisConnectionConfiguration.java

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,13 @@ protected final RedisStandaloneConfiguration getStandaloneConfig() {
6060
ConnectionInfo connectionInfo = parseUrl(this.properties.getUrl());
6161
config.setHostName(connectionInfo.getHostName());
6262
config.setPort(connectionInfo.getPort());
63+
config.setUsername(connectionInfo.getUsername());
6364
config.setPassword(RedisPassword.of(connectionInfo.getPassword()));
6465
}
6566
else {
6667
config.setHostName(this.properties.getHost());
6768
config.setPort(this.properties.getPort());
69+
config.setUsername(this.properties.getUsername());
6870
config.setPassword(RedisPassword.of(this.properties.getPassword()));
6971
}
7072
config.setDatabase(this.properties.getDatabase());
@@ -80,6 +82,7 @@ protected final RedisSentinelConfiguration getSentinelConfig() {
8082
RedisSentinelConfiguration config = new RedisSentinelConfiguration();
8183
config.master(sentinelProperties.getMaster());
8284
config.setSentinels(createSentinels(sentinelProperties));
85+
config.setUsername(this.properties.getUsername());
8386
if (this.properties.getPassword() != null) {
8487
config.setPassword(RedisPassword.of(this.properties.getPassword()));
8588
}
@@ -108,6 +111,7 @@ protected final RedisClusterConfiguration getClusterConfiguration() {
108111
if (clusterProperties.getMaxRedirects() != null) {
109112
config.setMaxRedirects(clusterProperties.getMaxRedirects());
110113
}
114+
config.setUsername(this.properties.getUsername());
111115
if (this.properties.getPassword() != null) {
112116
config.setPassword(RedisPassword.of(this.properties.getPassword()));
113117
}
@@ -141,15 +145,20 @@ protected ConnectionInfo parseUrl(String url) {
141145
throw new RedisUrlSyntaxException(url);
142146
}
143147
boolean useSsl = ("rediss".equals(scheme));
148+
String username = null;
144149
String password = null;
145150
if (uri.getUserInfo() != null) {
146-
password = uri.getUserInfo();
147-
int index = password.indexOf(':');
151+
String candidate = uri.getUserInfo();
152+
int index = candidate.indexOf(':');
148153
if (index >= 0) {
149-
password = password.substring(index + 1);
154+
username = candidate.substring(0, index);
155+
password = candidate.substring(index + 1);
156+
}
157+
else {
158+
password = candidate;
150159
}
151160
}
152-
return new ConnectionInfo(uri, useSsl, password);
161+
return new ConnectionInfo(uri, useSsl, username, password);
153162
}
154163
catch (URISyntaxException ex) {
155164
throw new RedisUrlSyntaxException(url, ex);
@@ -162,11 +171,14 @@ static class ConnectionInfo {
162171

163172
private final boolean useSsl;
164173

174+
private final String username;
175+
165176
private final String password;
166177

167-
ConnectionInfo(URI uri, boolean useSsl, String password) {
178+
ConnectionInfo(URI uri, boolean useSsl, String username, String password) {
168179
this.uri = uri;
169180
this.useSsl = useSsl;
181+
this.username = username;
170182
this.password = password;
171183
}
172184

@@ -182,6 +194,10 @@ int getPort() {
182194
return this.uri.getPort();
183195
}
184196

197+
String getUsername() {
198+
return this.username;
199+
}
200+
185201
String getPassword() {
186202
return this.password;
187203
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisProperties.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ public class RedisProperties {
5151
*/
5252
private String host = "localhost";
5353

54+
/**
55+
* Login username of the redis server.
56+
*/
57+
private String username;
58+
5459
/**
5560
* Login password of the redis server.
5661
*/
@@ -118,6 +123,14 @@ public void setHost(String host) {
118123
this.host = host;
119124
}
120125

126+
public String getUsername() {
127+
return this.username;
128+
}
129+
130+
public void setUsername(String username) {
131+
this.username = username;
132+
}
133+
121134
public String getPassword() {
122135
return this.password;
123136
}

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationJedisTests.java

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.springframework.data.redis.connection.RedisConnectionFactory;
2929
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration.JedisClientConfigurationBuilder;
3030
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
31+
import org.springframework.test.util.ReflectionTestUtils;
3132

3233
import static org.assertj.core.api.Assertions.assertThat;
3334

@@ -61,6 +62,7 @@ void testOverrideRedisConfiguration() {
6162
JedisConnectionFactory cf = context.getBean(JedisConnectionFactory.class);
6263
assertThat(cf.getHostName()).isEqualTo("foo");
6364
assertThat(cf.getDatabase()).isEqualTo(1);
65+
assertThat(getUserName(cf)).isNull();
6466
assertThat(cf.getPassword()).isNull();
6567
assertThat(cf.isUseSsl()).isFalse();
6668
});
@@ -82,6 +84,7 @@ void testRedisUrlConfiguration() {
8284
JedisConnectionFactory cf = context.getBean(JedisConnectionFactory.class);
8385
assertThat(cf.getHostName()).isEqualTo("example");
8486
assertThat(cf.getPort()).isEqualTo(33);
87+
assertThat(getUserName(cf)).isEqualTo("user");
8588
assertThat(cf.getPassword()).isEqualTo("password");
8689
assertThat(cf.isUseSsl()).isFalse();
8790
});
@@ -96,6 +99,7 @@ void testOverrideUrlRedisConfiguration() {
9699
JedisConnectionFactory cf = context.getBean(JedisConnectionFactory.class);
97100
assertThat(cf.getHostName()).isEqualTo("example");
98101
assertThat(cf.getPort()).isEqualTo(33);
102+
assertThat(getUserName(cf)).isEqualTo("user");
99103
assertThat(cf.getPassword()).isEqualTo("password");
100104
assertThat(cf.isUseSsl()).isTrue();
101105
});
@@ -104,18 +108,22 @@ void testOverrideUrlRedisConfiguration() {
104108
@Test
105109
void testPasswordInUrlWithColon() {
106110
this.contextRunner.withPropertyValues("spring.redis.url:redis://:pass:word@example:33").run((context) -> {
107-
assertThat(context.getBean(JedisConnectionFactory.class).getHostName()).isEqualTo("example");
108-
assertThat(context.getBean(JedisConnectionFactory.class).getPort()).isEqualTo(33);
109-
assertThat(context.getBean(JedisConnectionFactory.class).getPassword()).isEqualTo("pass:word");
111+
JedisConnectionFactory cf = context.getBean(JedisConnectionFactory.class);
112+
assertThat(cf.getHostName()).isEqualTo("example");
113+
assertThat(cf.getPort()).isEqualTo(33);
114+
assertThat(getUserName(cf)).isEqualTo("");
115+
assertThat(cf.getPassword()).isEqualTo("pass:word");
110116
});
111117
}
112118

113119
@Test
114120
void testPasswordInUrlStartsWithColon() {
115121
this.contextRunner.withPropertyValues("spring.redis.url:redis://user::pass:word@example:33").run((context) -> {
116-
assertThat(context.getBean(JedisConnectionFactory.class).getHostName()).isEqualTo("example");
117-
assertThat(context.getBean(JedisConnectionFactory.class).getPort()).isEqualTo(33);
118-
assertThat(context.getBean(JedisConnectionFactory.class).getPassword()).isEqualTo(":pass:word");
122+
JedisConnectionFactory cf = context.getBean(JedisConnectionFactory.class);
123+
assertThat(cf.getHostName()).isEqualTo("example");
124+
assertThat(cf.getPort()).isEqualTo(33);
125+
assertThat(getUserName(cf)).isEqualTo("user");
126+
assertThat(cf.getPassword()).isEqualTo(":pass:word");
119127
});
120128
}
121129

@@ -178,13 +186,13 @@ void testRedisConfigurationWithSentinel() {
178186
}
179187

180188
@Test
181-
void testRedisConfigurationWithSentinelAndPassword() {
182-
this.contextRunner
183-
.withPropertyValues("spring.redis.password=password", "spring.redis.sentinel.master:mymaster",
184-
"spring.redis.sentinel.nodes:127.0.0.1:26379,127.0.0.1:26380")
189+
void testRedisConfigurationWithSentinelAndAuthentication() {
190+
this.contextRunner.withPropertyValues("spring.redis.username=user", "spring.redis.password=password",
191+
"spring.redis.sentinel.master:mymaster", "spring.redis.sentinel.nodes:127.0.0.1:26379,127.0.0.1:26380")
185192
.withUserConfiguration(JedisConnectionFactoryCaptorConfiguration.class).run((context) -> {
186193
assertThat(context).hasFailed();
187194
assertThat(JedisConnectionFactoryCaptor.connectionFactory.isRedisSentinelAware()).isTrue();
195+
assertThat(getUserName(JedisConnectionFactoryCaptor.connectionFactory)).isEqualTo("user");
188196
assertThat(JedisConnectionFactoryCaptor.connectionFactory.getPassword()).isEqualTo("password");
189197
});
190198
}
@@ -196,6 +204,10 @@ void testRedisConfigurationWithCluster() {
196204
.isNotNull());
197205
}
198206

207+
private String getUserName(JedisConnectionFactory factory) {
208+
return ReflectionTestUtils.invokeMethod(factory, "getRedisUsername");
209+
}
210+
199211
@Configuration(proxyBeanMethods = false)
200212
static class CustomConfiguration {
201213

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ void testOverrideRedisConfiguration() {
8888
LettuceConnectionFactory cf = context.getBean(LettuceConnectionFactory.class);
8989
assertThat(cf.getHostName()).isEqualTo("foo");
9090
assertThat(cf.getDatabase()).isEqualTo(1);
91+
assertThat(getUserName(cf)).isNull();
9192
assertThat(cf.getPassword()).isNull();
9293
assertThat(cf.isUseSsl()).isFalse();
9394
assertThat(cf.getShutdownTimeout()).isEqualTo(500);
@@ -110,6 +111,7 @@ void testRedisUrlConfiguration() {
110111
LettuceConnectionFactory cf = context.getBean(LettuceConnectionFactory.class);
111112
assertThat(cf.getHostName()).isEqualTo("example");
112113
assertThat(cf.getPort()).isEqualTo(33);
114+
assertThat(getUserName(cf)).isEqualTo("user");
113115
assertThat(cf.getPassword()).isEqualTo("password");
114116
assertThat(cf.isUseSsl()).isFalse();
115117
});
@@ -124,6 +126,7 @@ void testOverrideUrlRedisConfiguration() {
124126
LettuceConnectionFactory cf = context.getBean(LettuceConnectionFactory.class);
125127
assertThat(cf.getHostName()).isEqualTo("example");
126128
assertThat(cf.getPort()).isEqualTo(33);
129+
assertThat(getUserName(cf)).isEqualTo("user");
127130
assertThat(cf.getPassword()).isEqualTo("password");
128131
assertThat(cf.isUseSsl()).isTrue();
129132
});
@@ -135,6 +138,7 @@ void testPasswordInUrlWithColon() {
135138
LettuceConnectionFactory cf = context.getBean(LettuceConnectionFactory.class);
136139
assertThat(cf.getHostName()).isEqualTo("example");
137140
assertThat(cf.getPort()).isEqualTo(33);
141+
assertThat(getUserName(cf)).isEqualTo("");
138142
assertThat(cf.getPassword()).isEqualTo("pass:word");
139143
});
140144
}
@@ -145,6 +149,7 @@ void testPasswordInUrlStartsWithColon() {
145149
LettuceConnectionFactory cf = context.getBean(LettuceConnectionFactory.class);
146150
assertThat(cf.getHostName()).isEqualTo("example");
147151
assertThat(cf.getPort()).isEqualTo(33);
152+
assertThat(getUserName(cf)).isEqualTo("user");
148153
assertThat(cf.getPassword()).isEqualTo(":pass:word");
149154
});
150155
}
@@ -237,10 +242,12 @@ void testRedisConfigurationWithSentinelAndDatabase() {
237242
}
238243

239244
@Test
240-
void testRedisConfigurationWithSentinelAndDataNodePassword() {
241-
this.contextRunner.withPropertyValues("spring.redis.password=password", "spring.redis.sentinel.master:mymaster",
245+
void testRedisConfigurationWithSentinelAndAuthentication() {
246+
this.contextRunner.withPropertyValues("spring.redis.username=user", "spring.redis.password=password",
247+
"spring.redis.sentinel.master:mymaster",
242248
"spring.redis.sentinel.nodes:127.0.0.1:26379, 127.0.0.1:26380").run((context) -> {
243249
LettuceConnectionFactory connectionFactory = context.getBean(LettuceConnectionFactory.class);
250+
assertThat(getUserName(connectionFactory)).isEqualTo("user");
244251
assertThat(connectionFactory.getPassword()).isEqualTo("password");
245252
RedisSentinelConfiguration sentinelConfiguration = connectionFactory.getSentinelConfiguration();
246253
assertThat(sentinelConfiguration.getSentinelPassword().isPresent()).isFalse();
@@ -256,6 +263,7 @@ void testRedisConfigurationWithSentinelPasswordAndDataNodePassword() {
256263
"spring.redis.sentinel.master:mymaster",
257264
"spring.redis.sentinel.nodes:127.0.0.1:26379, 127.0.0.1:26380").run((context) -> {
258265
LettuceConnectionFactory connectionFactory = context.getBean(LettuceConnectionFactory.class);
266+
assertThat(getUserName(connectionFactory)).isNull();
259267
assertThat(connectionFactory.getPassword()).isEqualTo("password");
260268
RedisSentinelConfiguration sentinelConfiguration = connectionFactory.getSentinelConfiguration();
261269
assertThat(new String(sentinelConfiguration.getSentinelPassword().get())).isEqualTo("secret");
@@ -292,16 +300,17 @@ void testRedisConfigurationWithCluster() {
292300
}
293301

294302
@Test
295-
void testRedisConfigurationWithClusterAndPassword() {
303+
void testRedisConfigurationWithClusterAndAuthentication() {
296304
List<String> clusterNodes = Arrays.asList("127.0.0.1:27379", "127.0.0.1:27380");
297-
this.contextRunner
298-
.withPropertyValues("spring.redis.password=password",
299-
"spring.redis.cluster.nodes[0]:" + clusterNodes.get(0),
300-
"spring.redis.cluster.nodes[1]:" + clusterNodes.get(1))
301-
.run((context) -> assertThat(context.getBean(LettuceConnectionFactory.class).getPassword())
302-
.isEqualTo("password")
305+
this.contextRunner.withPropertyValues("spring.redis.username=user", "spring.redis.password=password",
306+
"spring.redis.cluster.nodes[0]:" + clusterNodes.get(0),
307+
"spring.redis.cluster.nodes[1]:" + clusterNodes.get(1)).run((context) -> {
308+
LettuceConnectionFactory connectionFactory = context.getBean(LettuceConnectionFactory.class);
309+
assertThat(getUserName(connectionFactory)).isEqualTo("user");
310+
assertThat(connectionFactory.getPassword()).isEqualTo("password");
311+
}
303312

304-
);
313+
);
305314
}
306315

307316
@Test
@@ -393,6 +402,10 @@ private LettucePoolingClientConfiguration getPoolingClientConfiguration(LettuceC
393402
return (LettucePoolingClientConfiguration) ReflectionTestUtils.getField(factory, "clientConfiguration");
394403
}
395404

405+
private String getUserName(LettuceConnectionFactory factory) {
406+
return ReflectionTestUtils.invokeMethod(factory, "getRedisUsername");
407+
}
408+
396409
@Configuration(proxyBeanMethods = false)
397410
static class CustomConfiguration {
398411

0 commit comments

Comments
 (0)