Skip to content

Commit 1e2176b

Browse files
Improve Redis URL validation
This commit improves the validation of URLs provided in the property 'spring.redis.url' used to auto-configure a Spring Data Redis connection. In particular, only the URL schemes 'redis://' and 'rediss://' are allowed, and any other scheme will result in a configuration error. A failure analyzer is also provided to improve diagnostics for common mis-configurations detected by this validation. Fixes gh-21999
1 parent d84aeef commit 1e2176b

File tree

6 files changed

+204
-2
lines changed

6 files changed

+204
-2
lines changed

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
* @author Mark Paluch
3737
* @author Stephane Nicoll
3838
* @author Alen Turkovic
39+
* @author Scott Frederick
3940
*/
4041
abstract class RedisConnectionConfiguration {
4142

@@ -135,7 +136,11 @@ private List<RedisNode> createSentinels(RedisProperties.Sentinel sentinel) {
135136
protected ConnectionInfo parseUrl(String url) {
136137
try {
137138
URI uri = new URI(url);
138-
boolean useSsl = (url.startsWith("rediss://"));
139+
String scheme = uri.getScheme();
140+
if (!"redis".equals(scheme) && !"rediss".equals(scheme)) {
141+
throw new RedisUrlSyntaxException(url);
142+
}
143+
boolean useSsl = ("rediss".equals(scheme));
139144
String password = null;
140145
if (uri.getUserInfo() != null) {
141146
password = uri.getUserInfo();
@@ -147,7 +152,7 @@ protected ConnectionInfo parseUrl(String url) {
147152
return new ConnectionInfo(uri, useSsl, password);
148153
}
149154
catch (URISyntaxException ex) {
150-
throw new IllegalArgumentException("Malformed url '" + url + "'", ex);
155+
throw new RedisUrlSyntaxException(url, ex);
151156
}
152157
}
153158

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2012-2020 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+
* https://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.data.redis;
18+
19+
/**
20+
* Exception thrown when a Redis URL is malformed or invalid.
21+
*
22+
* @author Scott Frederick
23+
*/
24+
class RedisUrlSyntaxException extends RuntimeException {
25+
26+
private final String url;
27+
28+
RedisUrlSyntaxException(String url, Exception cause) {
29+
super(buildMessage(url), cause);
30+
this.url = url;
31+
}
32+
33+
RedisUrlSyntaxException(String url) {
34+
super(buildMessage(url));
35+
this.url = url;
36+
}
37+
38+
String getUrl() {
39+
return this.url;
40+
}
41+
42+
private static String buildMessage(String url) {
43+
return "Invalid Redis URL '" + url + "'";
44+
}
45+
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2012-2020 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+
* https://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.data.redis;
18+
19+
import java.net.URI;
20+
import java.net.URISyntaxException;
21+
22+
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
23+
import org.springframework.boot.diagnostics.FailureAnalysis;
24+
25+
/**
26+
* A {@code FailureAnalyzer} that performs analysis of failures caused by a
27+
* {@link RedisUrlSyntaxException}.
28+
*
29+
* @author Scott Frederick
30+
*/
31+
class RedisUrlSyntaxFailureAnalyzer extends AbstractFailureAnalyzer<RedisUrlSyntaxException> {
32+
33+
@Override
34+
protected FailureAnalysis analyze(Throwable rootFailure, RedisUrlSyntaxException cause) {
35+
try {
36+
URI uri = new URI(cause.getUrl());
37+
if ("redis-sentinel".equals(uri.getScheme())) {
38+
return new FailureAnalysis(getUnsupportedSchemeDescription(cause.getUrl(), uri.getScheme()),
39+
"Use spring.redis.sentinel properties instead of spring.redis.url to configure Redis sentinel addresses.",
40+
cause);
41+
}
42+
if ("redis-socket".equals(uri.getScheme())) {
43+
return new FailureAnalysis(getUnsupportedSchemeDescription(cause.getUrl(), uri.getScheme()),
44+
"Configure the appropriate Spring Data Redis connection beans directly instead of setting the property 'spring.redis.url'.",
45+
cause);
46+
}
47+
if (!"redis".equals(uri.getScheme()) && !"rediss".equals(uri.getScheme())) {
48+
return new FailureAnalysis(getUnsupportedSchemeDescription(cause.getUrl(), uri.getScheme()),
49+
"Use the scheme 'redis://` for insecure or `rediss://` for secure Redis standalone configuration.",
50+
cause);
51+
}
52+
}
53+
catch (URISyntaxException ex) {
54+
// fall through to default description and action
55+
}
56+
return new FailureAnalysis(getDefaultDescription(cause.getUrl()),
57+
"Review the value of the property 'spring.redis.url'.", cause);
58+
}
59+
60+
private String getDefaultDescription(String url) {
61+
return "The URL '" + url + "' is not valid for configuring Spring Data Redis. ";
62+
}
63+
64+
private String getUnsupportedSchemeDescription(String url, String scheme) {
65+
return getDefaultDescription(url) + "The scheme '" + scheme + "' is not supported.";
66+
}
67+
68+
}

spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAuto
149149

150150
# Failure analyzers
151151
org.springframework.boot.diagnostics.FailureAnalyzer=\
152+
org.springframework.boot.autoconfigure.data.redis.RedisUrlSyntaxFailureAnalyzer,\
152153
org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\
153154
org.springframework.boot.autoconfigure.flyway.FlywayMigrationScriptMissingFailureAnalyzer,\
154155
org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import org.springframework.util.StringUtils;
4949

5050
import static org.assertj.core.api.Assertions.assertThat;
51+
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
5152

5253
/**
5354
* Tests for {@link RedisAutoConfiguration}.
@@ -60,6 +61,7 @@
6061
* @author Mark Paluch
6162
* @author Stephane Nicoll
6263
* @author Alen Turkovic
64+
* @author Scott Frederick
6365
*/
6466
class RedisAutoConfigurationTests {
6567

@@ -228,6 +230,17 @@ void testRedisConfigurationWithSentinelPasswordAndDataNodePassword() {
228230
});
229231
}
230232

233+
@Test
234+
void testRedisSentinelUrlConfiguration() {
235+
this.contextRunner
236+
.withPropertyValues(
237+
"spring.redis.url=redis-sentinel://username:[email protected]:26379,127.0.0.1:26380/mymaster")
238+
.run((context) -> assertThatIllegalStateException()
239+
.isThrownBy(() -> context.getBean(LettuceConnectionFactory.class))
240+
.withRootCauseInstanceOf(RedisUrlSyntaxException.class).havingRootCause().withMessageContaining(
241+
"Invalid Redis URL 'redis-sentinel://username:[email protected]:26379,127.0.0.1:26380/mymaster'"));
242+
}
243+
231244
@Test
232245
void testRedisConfigurationWithCluster() {
233246
List<String> clusterNodes = Arrays.asList("127.0.0.1:27379", "127.0.0.1:27380");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2012-2020 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+
* https://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.data.redis;
18+
19+
import org.junit.jupiter.api.Test;
20+
21+
import org.springframework.boot.diagnostics.FailureAnalysis;
22+
23+
import static org.assertj.core.api.Assertions.assertThat;
24+
25+
/**
26+
* Tests for {@link RedisUrlSyntaxFailureAnalyzer}.
27+
*
28+
* @author Scott Frederick
29+
*/
30+
class RedisUrlSyntaxFailureAnalyzerTests {
31+
32+
@Test
33+
void analyzeInvalidUrlSyntax() {
34+
RedisUrlSyntaxException exception = new RedisUrlSyntaxException("redis://invalid");
35+
FailureAnalysis analysis = new RedisUrlSyntaxFailureAnalyzer().analyze(exception);
36+
assertThat(analysis.getDescription()).contains("The URL 'redis://invalid' is not valid");
37+
assertThat(analysis.getAction()).contains("Review the value of the property 'spring.redis.url'");
38+
}
39+
40+
@Test
41+
void analyzeRedisHttpUrl() {
42+
RedisUrlSyntaxException exception = new RedisUrlSyntaxException("http://127.0.0.1:26379/mymaster");
43+
FailureAnalysis analysis = new RedisUrlSyntaxFailureAnalyzer().analyze(exception);
44+
assertThat(analysis.getDescription()).contains("The URL 'http://127.0.0.1:26379/mymaster' is not valid")
45+
.contains("The scheme 'http' is not supported");
46+
assertThat(analysis.getAction()).contains("Use the scheme 'redis://` for insecure or `rediss://` for secure");
47+
}
48+
49+
@Test
50+
void analyzeRedisSentinelUrl() {
51+
RedisUrlSyntaxException exception = new RedisUrlSyntaxException(
52+
"redis-sentinel://username:[email protected]:26379,127.0.0.1:26380/mymaster");
53+
FailureAnalysis analysis = new RedisUrlSyntaxFailureAnalyzer().analyze(exception);
54+
assertThat(analysis.getDescription()).contains(
55+
"The URL 'redis-sentinel://username:[email protected]:26379,127.0.0.1:26380/mymaster' is not valid")
56+
.contains("The scheme 'redis-sentinel' is not supported");
57+
assertThat(analysis.getAction()).contains("Use spring.redis.sentinel properties");
58+
}
59+
60+
@Test
61+
void analyzeRedisSocketUrl() {
62+
RedisUrlSyntaxException exception = new RedisUrlSyntaxException("redis-socket:///redis/redis.sock");
63+
FailureAnalysis analysis = new RedisUrlSyntaxFailureAnalyzer().analyze(exception);
64+
assertThat(analysis.getDescription()).contains("The URL 'redis-socket:///redis/redis.sock' is not valid")
65+
.contains("The scheme 'redis-socket' is not supported");
66+
assertThat(analysis.getAction()).contains("Configure the appropriate Spring Data Redis connection beans");
67+
}
68+
69+
}

0 commit comments

Comments
 (0)