Add support for Redis 6 authentication with username

Closes gh-22702
pull/23540/head
Stephane Nicoll 4 years ago
parent a5c20a5132
commit 36382e599c

@ -60,11 +60,13 @@ abstract class RedisConnectionConfiguration {
ConnectionInfo connectionInfo = parseUrl(this.properties.getUrl()); ConnectionInfo connectionInfo = parseUrl(this.properties.getUrl());
config.setHostName(connectionInfo.getHostName()); config.setHostName(connectionInfo.getHostName());
config.setPort(connectionInfo.getPort()); config.setPort(connectionInfo.getPort());
config.setUsername(connectionInfo.getUsername());
config.setPassword(RedisPassword.of(connectionInfo.getPassword())); config.setPassword(RedisPassword.of(connectionInfo.getPassword()));
} }
else { else {
config.setHostName(this.properties.getHost()); config.setHostName(this.properties.getHost());
config.setPort(this.properties.getPort()); config.setPort(this.properties.getPort());
config.setUsername(this.properties.getUsername());
config.setPassword(RedisPassword.of(this.properties.getPassword())); config.setPassword(RedisPassword.of(this.properties.getPassword()));
} }
config.setDatabase(this.properties.getDatabase()); config.setDatabase(this.properties.getDatabase());
@ -80,6 +82,7 @@ abstract class RedisConnectionConfiguration {
RedisSentinelConfiguration config = new RedisSentinelConfiguration(); RedisSentinelConfiguration config = new RedisSentinelConfiguration();
config.master(sentinelProperties.getMaster()); config.master(sentinelProperties.getMaster());
config.setSentinels(createSentinels(sentinelProperties)); config.setSentinels(createSentinels(sentinelProperties));
config.setUsername(this.properties.getUsername());
if (this.properties.getPassword() != null) { if (this.properties.getPassword() != null) {
config.setPassword(RedisPassword.of(this.properties.getPassword())); config.setPassword(RedisPassword.of(this.properties.getPassword()));
} }
@ -108,6 +111,7 @@ abstract class RedisConnectionConfiguration {
if (clusterProperties.getMaxRedirects() != null) { if (clusterProperties.getMaxRedirects() != null) {
config.setMaxRedirects(clusterProperties.getMaxRedirects()); config.setMaxRedirects(clusterProperties.getMaxRedirects());
} }
config.setUsername(this.properties.getUsername());
if (this.properties.getPassword() != null) { if (this.properties.getPassword() != null) {
config.setPassword(RedisPassword.of(this.properties.getPassword())); config.setPassword(RedisPassword.of(this.properties.getPassword()));
} }
@ -141,15 +145,20 @@ abstract class RedisConnectionConfiguration {
throw new RedisUrlSyntaxException(url); throw new RedisUrlSyntaxException(url);
} }
boolean useSsl = ("rediss".equals(scheme)); boolean useSsl = ("rediss".equals(scheme));
String username = null;
String password = null; String password = null;
if (uri.getUserInfo() != null) { if (uri.getUserInfo() != null) {
password = uri.getUserInfo(); String candidate = uri.getUserInfo();
int index = password.indexOf(':'); int index = candidate.indexOf(':');
if (index >= 0) { if (index >= 0) {
password = password.substring(index + 1); username = candidate.substring(0, index);
password = candidate.substring(index + 1);
}
else {
password = candidate;
} }
} }
return new ConnectionInfo(uri, useSsl, password); return new ConnectionInfo(uri, useSsl, username, password);
} }
catch (URISyntaxException ex) { catch (URISyntaxException ex) {
throw new RedisUrlSyntaxException(url, ex); throw new RedisUrlSyntaxException(url, ex);
@ -162,11 +171,14 @@ abstract class RedisConnectionConfiguration {
private final boolean useSsl; private final boolean useSsl;
private final String username;
private final String password; private final String password;
ConnectionInfo(URI uri, boolean useSsl, String password) { ConnectionInfo(URI uri, boolean useSsl, String username, String password) {
this.uri = uri; this.uri = uri;
this.useSsl = useSsl; this.useSsl = useSsl;
this.username = username;
this.password = password; this.password = password;
} }
@ -182,6 +194,10 @@ abstract class RedisConnectionConfiguration {
return this.uri.getPort(); return this.uri.getPort();
} }
String getUsername() {
return this.username;
}
String getPassword() { String getPassword() {
return this.password; return this.password;
} }

@ -51,6 +51,11 @@ public class RedisProperties {
*/ */
private String host = "localhost"; private String host = "localhost";
/**
* Login username of the redis server.
*/
private String username;
/** /**
* Login password of the redis server. * Login password of the redis server.
*/ */
@ -118,6 +123,14 @@ public class RedisProperties {
this.host = host; this.host = host;
} }
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() { public String getPassword() {
return this.password; return this.password;
} }

@ -28,6 +28,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration.JedisClientConfigurationBuilder; import org.springframework.data.redis.connection.jedis.JedisClientConfiguration.JedisClientConfigurationBuilder;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -61,6 +62,7 @@ class RedisAutoConfigurationJedisTests {
JedisConnectionFactory cf = context.getBean(JedisConnectionFactory.class); JedisConnectionFactory cf = context.getBean(JedisConnectionFactory.class);
assertThat(cf.getHostName()).isEqualTo("foo"); assertThat(cf.getHostName()).isEqualTo("foo");
assertThat(cf.getDatabase()).isEqualTo(1); assertThat(cf.getDatabase()).isEqualTo(1);
assertThat(getUserName(cf)).isNull();
assertThat(cf.getPassword()).isNull(); assertThat(cf.getPassword()).isNull();
assertThat(cf.isUseSsl()).isFalse(); assertThat(cf.isUseSsl()).isFalse();
}); });
@ -82,6 +84,7 @@ class RedisAutoConfigurationJedisTests {
JedisConnectionFactory cf = context.getBean(JedisConnectionFactory.class); JedisConnectionFactory cf = context.getBean(JedisConnectionFactory.class);
assertThat(cf.getHostName()).isEqualTo("example"); assertThat(cf.getHostName()).isEqualTo("example");
assertThat(cf.getPort()).isEqualTo(33); assertThat(cf.getPort()).isEqualTo(33);
assertThat(getUserName(cf)).isEqualTo("user");
assertThat(cf.getPassword()).isEqualTo("password"); assertThat(cf.getPassword()).isEqualTo("password");
assertThat(cf.isUseSsl()).isFalse(); assertThat(cf.isUseSsl()).isFalse();
}); });
@ -96,6 +99,7 @@ class RedisAutoConfigurationJedisTests {
JedisConnectionFactory cf = context.getBean(JedisConnectionFactory.class); JedisConnectionFactory cf = context.getBean(JedisConnectionFactory.class);
assertThat(cf.getHostName()).isEqualTo("example"); assertThat(cf.getHostName()).isEqualTo("example");
assertThat(cf.getPort()).isEqualTo(33); assertThat(cf.getPort()).isEqualTo(33);
assertThat(getUserName(cf)).isEqualTo("user");
assertThat(cf.getPassword()).isEqualTo("password"); assertThat(cf.getPassword()).isEqualTo("password");
assertThat(cf.isUseSsl()).isTrue(); assertThat(cf.isUseSsl()).isTrue();
}); });
@ -104,18 +108,22 @@ class RedisAutoConfigurationJedisTests {
@Test @Test
void testPasswordInUrlWithColon() { void testPasswordInUrlWithColon() {
this.contextRunner.withPropertyValues("spring.redis.url:redis://:pass:word@example:33").run((context) -> { this.contextRunner.withPropertyValues("spring.redis.url:redis://:pass:word@example:33").run((context) -> {
assertThat(context.getBean(JedisConnectionFactory.class).getHostName()).isEqualTo("example"); JedisConnectionFactory cf = context.getBean(JedisConnectionFactory.class);
assertThat(context.getBean(JedisConnectionFactory.class).getPort()).isEqualTo(33); assertThat(cf.getHostName()).isEqualTo("example");
assertThat(context.getBean(JedisConnectionFactory.class).getPassword()).isEqualTo("pass:word"); assertThat(cf.getPort()).isEqualTo(33);
assertThat(getUserName(cf)).isEqualTo("");
assertThat(cf.getPassword()).isEqualTo("pass:word");
}); });
} }
@Test @Test
void testPasswordInUrlStartsWithColon() { void testPasswordInUrlStartsWithColon() {
this.contextRunner.withPropertyValues("spring.redis.url:redis://user::pass:word@example:33").run((context) -> { this.contextRunner.withPropertyValues("spring.redis.url:redis://user::pass:word@example:33").run((context) -> {
assertThat(context.getBean(JedisConnectionFactory.class).getHostName()).isEqualTo("example"); JedisConnectionFactory cf = context.getBean(JedisConnectionFactory.class);
assertThat(context.getBean(JedisConnectionFactory.class).getPort()).isEqualTo(33); assertThat(cf.getHostName()).isEqualTo("example");
assertThat(context.getBean(JedisConnectionFactory.class).getPassword()).isEqualTo(":pass:word"); assertThat(cf.getPort()).isEqualTo(33);
assertThat(getUserName(cf)).isEqualTo("user");
assertThat(cf.getPassword()).isEqualTo(":pass:word");
}); });
} }
@ -178,13 +186,13 @@ class RedisAutoConfigurationJedisTests {
} }
@Test @Test
void testRedisConfigurationWithSentinelAndPassword() { void testRedisConfigurationWithSentinelAndAuthentication() {
this.contextRunner this.contextRunner.withPropertyValues("spring.redis.username=user", "spring.redis.password=password",
.withPropertyValues("spring.redis.password=password", "spring.redis.sentinel.master:mymaster", "spring.redis.sentinel.master:mymaster", "spring.redis.sentinel.nodes:127.0.0.1:26379,127.0.0.1:26380")
"spring.redis.sentinel.nodes:127.0.0.1:26379,127.0.0.1:26380")
.withUserConfiguration(JedisConnectionFactoryCaptorConfiguration.class).run((context) -> { .withUserConfiguration(JedisConnectionFactoryCaptorConfiguration.class).run((context) -> {
assertThat(context).hasFailed(); assertThat(context).hasFailed();
assertThat(JedisConnectionFactoryCaptor.connectionFactory.isRedisSentinelAware()).isTrue(); assertThat(JedisConnectionFactoryCaptor.connectionFactory.isRedisSentinelAware()).isTrue();
assertThat(getUserName(JedisConnectionFactoryCaptor.connectionFactory)).isEqualTo("user");
assertThat(JedisConnectionFactoryCaptor.connectionFactory.getPassword()).isEqualTo("password"); assertThat(JedisConnectionFactoryCaptor.connectionFactory.getPassword()).isEqualTo("password");
}); });
} }
@ -196,6 +204,10 @@ class RedisAutoConfigurationJedisTests {
.isNotNull()); .isNotNull());
} }
private String getUserName(JedisConnectionFactory factory) {
return ReflectionTestUtils.invokeMethod(factory, "getRedisUsername");
}
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
static class CustomConfiguration { static class CustomConfiguration {

@ -88,6 +88,7 @@ class RedisAutoConfigurationTests {
LettuceConnectionFactory cf = context.getBean(LettuceConnectionFactory.class); LettuceConnectionFactory cf = context.getBean(LettuceConnectionFactory.class);
assertThat(cf.getHostName()).isEqualTo("foo"); assertThat(cf.getHostName()).isEqualTo("foo");
assertThat(cf.getDatabase()).isEqualTo(1); assertThat(cf.getDatabase()).isEqualTo(1);
assertThat(getUserName(cf)).isNull();
assertThat(cf.getPassword()).isNull(); assertThat(cf.getPassword()).isNull();
assertThat(cf.isUseSsl()).isFalse(); assertThat(cf.isUseSsl()).isFalse();
assertThat(cf.getShutdownTimeout()).isEqualTo(500); assertThat(cf.getShutdownTimeout()).isEqualTo(500);
@ -110,6 +111,7 @@ class RedisAutoConfigurationTests {
LettuceConnectionFactory cf = context.getBean(LettuceConnectionFactory.class); LettuceConnectionFactory cf = context.getBean(LettuceConnectionFactory.class);
assertThat(cf.getHostName()).isEqualTo("example"); assertThat(cf.getHostName()).isEqualTo("example");
assertThat(cf.getPort()).isEqualTo(33); assertThat(cf.getPort()).isEqualTo(33);
assertThat(getUserName(cf)).isEqualTo("user");
assertThat(cf.getPassword()).isEqualTo("password"); assertThat(cf.getPassword()).isEqualTo("password");
assertThat(cf.isUseSsl()).isFalse(); assertThat(cf.isUseSsl()).isFalse();
}); });
@ -124,6 +126,7 @@ class RedisAutoConfigurationTests {
LettuceConnectionFactory cf = context.getBean(LettuceConnectionFactory.class); LettuceConnectionFactory cf = context.getBean(LettuceConnectionFactory.class);
assertThat(cf.getHostName()).isEqualTo("example"); assertThat(cf.getHostName()).isEqualTo("example");
assertThat(cf.getPort()).isEqualTo(33); assertThat(cf.getPort()).isEqualTo(33);
assertThat(getUserName(cf)).isEqualTo("user");
assertThat(cf.getPassword()).isEqualTo("password"); assertThat(cf.getPassword()).isEqualTo("password");
assertThat(cf.isUseSsl()).isTrue(); assertThat(cf.isUseSsl()).isTrue();
}); });
@ -135,6 +138,7 @@ class RedisAutoConfigurationTests {
LettuceConnectionFactory cf = context.getBean(LettuceConnectionFactory.class); LettuceConnectionFactory cf = context.getBean(LettuceConnectionFactory.class);
assertThat(cf.getHostName()).isEqualTo("example"); assertThat(cf.getHostName()).isEqualTo("example");
assertThat(cf.getPort()).isEqualTo(33); assertThat(cf.getPort()).isEqualTo(33);
assertThat(getUserName(cf)).isEqualTo("");
assertThat(cf.getPassword()).isEqualTo("pass:word"); assertThat(cf.getPassword()).isEqualTo("pass:word");
}); });
} }
@ -145,6 +149,7 @@ class RedisAutoConfigurationTests {
LettuceConnectionFactory cf = context.getBean(LettuceConnectionFactory.class); LettuceConnectionFactory cf = context.getBean(LettuceConnectionFactory.class);
assertThat(cf.getHostName()).isEqualTo("example"); assertThat(cf.getHostName()).isEqualTo("example");
assertThat(cf.getPort()).isEqualTo(33); assertThat(cf.getPort()).isEqualTo(33);
assertThat(getUserName(cf)).isEqualTo("user");
assertThat(cf.getPassword()).isEqualTo(":pass:word"); assertThat(cf.getPassword()).isEqualTo(":pass:word");
}); });
} }
@ -237,10 +242,12 @@ class RedisAutoConfigurationTests {
} }
@Test @Test
void testRedisConfigurationWithSentinelAndDataNodePassword() { void testRedisConfigurationWithSentinelAndAuthentication() {
this.contextRunner.withPropertyValues("spring.redis.password=password", "spring.redis.sentinel.master:mymaster", this.contextRunner.withPropertyValues("spring.redis.username=user", "spring.redis.password=password",
"spring.redis.sentinel.master:mymaster",
"spring.redis.sentinel.nodes:127.0.0.1:26379, 127.0.0.1:26380").run((context) -> { "spring.redis.sentinel.nodes:127.0.0.1:26379, 127.0.0.1:26380").run((context) -> {
LettuceConnectionFactory connectionFactory = context.getBean(LettuceConnectionFactory.class); LettuceConnectionFactory connectionFactory = context.getBean(LettuceConnectionFactory.class);
assertThat(getUserName(connectionFactory)).isEqualTo("user");
assertThat(connectionFactory.getPassword()).isEqualTo("password"); assertThat(connectionFactory.getPassword()).isEqualTo("password");
RedisSentinelConfiguration sentinelConfiguration = connectionFactory.getSentinelConfiguration(); RedisSentinelConfiguration sentinelConfiguration = connectionFactory.getSentinelConfiguration();
assertThat(sentinelConfiguration.getSentinelPassword().isPresent()).isFalse(); assertThat(sentinelConfiguration.getSentinelPassword().isPresent()).isFalse();
@ -256,6 +263,7 @@ class RedisAutoConfigurationTests {
"spring.redis.sentinel.master:mymaster", "spring.redis.sentinel.master:mymaster",
"spring.redis.sentinel.nodes:127.0.0.1:26379, 127.0.0.1:26380").run((context) -> { "spring.redis.sentinel.nodes:127.0.0.1:26379, 127.0.0.1:26380").run((context) -> {
LettuceConnectionFactory connectionFactory = context.getBean(LettuceConnectionFactory.class); LettuceConnectionFactory connectionFactory = context.getBean(LettuceConnectionFactory.class);
assertThat(getUserName(connectionFactory)).isNull();
assertThat(connectionFactory.getPassword()).isEqualTo("password"); assertThat(connectionFactory.getPassword()).isEqualTo("password");
RedisSentinelConfiguration sentinelConfiguration = connectionFactory.getSentinelConfiguration(); RedisSentinelConfiguration sentinelConfiguration = connectionFactory.getSentinelConfiguration();
assertThat(new String(sentinelConfiguration.getSentinelPassword().get())).isEqualTo("secret"); assertThat(new String(sentinelConfiguration.getSentinelPassword().get())).isEqualTo("secret");
@ -292,14 +300,15 @@ class RedisAutoConfigurationTests {
} }
@Test @Test
void testRedisConfigurationWithClusterAndPassword() { void testRedisConfigurationWithClusterAndAuthentication() {
List<String> clusterNodes = Arrays.asList("127.0.0.1:27379", "127.0.0.1:27380"); List<String> clusterNodes = Arrays.asList("127.0.0.1:27379", "127.0.0.1:27380");
this.contextRunner this.contextRunner.withPropertyValues("spring.redis.username=user", "spring.redis.password=password",
.withPropertyValues("spring.redis.password=password",
"spring.redis.cluster.nodes[0]:" + clusterNodes.get(0), "spring.redis.cluster.nodes[0]:" + clusterNodes.get(0),
"spring.redis.cluster.nodes[1]:" + clusterNodes.get(1)) "spring.redis.cluster.nodes[1]:" + clusterNodes.get(1)).run((context) -> {
.run((context) -> assertThat(context.getBean(LettuceConnectionFactory.class).getPassword()) LettuceConnectionFactory connectionFactory = context.getBean(LettuceConnectionFactory.class);
.isEqualTo("password") assertThat(getUserName(connectionFactory)).isEqualTo("user");
assertThat(connectionFactory.getPassword()).isEqualTo("password");
}
); );
} }
@ -393,6 +402,10 @@ class RedisAutoConfigurationTests {
return (LettucePoolingClientConfiguration) ReflectionTestUtils.getField(factory, "clientConfiguration"); return (LettucePoolingClientConfiguration) ReflectionTestUtils.getField(factory, "clientConfiguration");
} }
private String getUserName(LettuceConnectionFactory factory) {
return ReflectionTestUtils.invokeMethod(factory, "getRedisUsername");
}
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
static class CustomConfiguration { static class CustomConfiguration {

Loading…
Cancel
Save