Skip to content

Commit 7cf9cc7

Browse files
committed
Add SSL service connection support for Redis
See gh-41137
1 parent b773dcd commit 7cf9cc7

File tree

19 files changed

+459
-102
lines changed

19 files changed

+459
-102
lines changed

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

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -92,21 +92,17 @@ JedisConnectionFactory redisConnectionFactoryVirtualThreads(
9292
private JedisConnectionFactory createJedisConnectionFactory(
9393
ObjectProvider<JedisClientConfigurationBuilderCustomizer> builderCustomizers) {
9494
JedisClientConfiguration clientConfiguration = getJedisClientConfiguration(builderCustomizers);
95-
if (getSentinelConfig() != null) {
96-
return new JedisConnectionFactory(getSentinelConfig(), clientConfiguration);
97-
}
98-
if (getClusterConfiguration() != null) {
99-
return new JedisConnectionFactory(getClusterConfiguration(), clientConfiguration);
100-
}
101-
return new JedisConnectionFactory(getStandaloneConfig(), clientConfiguration);
95+
return switch (this.mode) {
96+
case STANDALONE -> new JedisConnectionFactory(getStandaloneConfig(), clientConfiguration);
97+
case CLUSTER -> new JedisConnectionFactory(getClusterConfiguration(), clientConfiguration);
98+
case SENTINEL -> new JedisConnectionFactory(getSentinelConfig(), clientConfiguration);
99+
};
102100
}
103101

104102
private JedisClientConfiguration getJedisClientConfiguration(
105103
ObjectProvider<JedisClientConfigurationBuilderCustomizer> builderCustomizers) {
106104
JedisClientConfigurationBuilder builder = applyProperties(JedisClientConfiguration.builder());
107-
if (isSslEnabled()) {
108-
applySsl(builder);
109-
}
105+
applySslIfNeeded(builder);
110106
RedisProperties.Pool pool = getProperties().getJedis().getPool();
111107
if (isPoolEnabled(pool)) {
112108
applyPooling(pool, builder);
@@ -126,18 +122,19 @@ private JedisClientConfigurationBuilder applyProperties(JedisClientConfiguration
126122
return builder;
127123
}
128124

129-
private void applySsl(JedisClientConfigurationBuilder builder) {
130-
JedisSslClientConfigurationBuilder sslBuilder = builder.useSsl();
131-
if (getProperties().getSsl().getBundle() != null) {
132-
SslBundle sslBundle = getSslBundles().getBundle(getProperties().getSsl().getBundle());
133-
sslBuilder.sslSocketFactory(sslBundle.createSslContext().getSocketFactory());
134-
SslOptions sslOptions = sslBundle.getOptions();
135-
SSLParameters sslParameters = new SSLParameters();
136-
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
137-
map.from(sslOptions.getCiphers()).to(sslParameters::setCipherSuites);
138-
map.from(sslOptions.getEnabledProtocols()).to(sslParameters::setProtocols);
139-
sslBuilder.sslParameters(sslParameters);
125+
private void applySslIfNeeded(JedisClientConfigurationBuilder builder) {
126+
SslBundle sslBundle = getSslBundle();
127+
if (sslBundle == null) {
128+
return;
140129
}
130+
JedisSslClientConfigurationBuilder sslBuilder = builder.useSsl();
131+
sslBuilder.sslSocketFactory(sslBundle.createSslContext().getSocketFactory());
132+
SslOptions sslOptions = sslBundle.getOptions();
133+
SSLParameters sslParameters = new SSLParameters();
134+
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
135+
map.from(sslOptions.getCiphers()).to(sslParameters::setCipherSuites);
136+
map.from(sslOptions.getEnabledProtocols()).to(sslParameters::setProtocols);
137+
sslBuilder.sslParameters(sslParameters);
141138
}
142139

143140
private void applyPooling(RedisProperties.Pool pool,

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

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -116,31 +116,27 @@ private LettuceConnectionFactory createConnectionFactory(
116116
ObjectProvider<LettuceClientConfigurationBuilderCustomizer> clientConfigurationBuilderCustomizers,
117117
ObjectProvider<LettuceClientOptionsBuilderCustomizer> clientOptionsBuilderCustomizers,
118118
ClientResources clientResources) {
119-
LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(clientConfigurationBuilderCustomizers,
120-
clientOptionsBuilderCustomizers, clientResources, getProperties().getLettuce().getPool());
121-
return createLettuceConnectionFactory(clientConfig);
122-
}
123-
124-
private LettuceConnectionFactory createLettuceConnectionFactory(LettuceClientConfiguration clientConfiguration) {
125-
if (getSentinelConfig() != null) {
126-
return new LettuceConnectionFactory(getSentinelConfig(), clientConfiguration);
127-
}
128-
if (getClusterConfiguration() != null) {
129-
return new LettuceConnectionFactory(getClusterConfiguration(), clientConfiguration);
130-
}
131-
return new LettuceConnectionFactory(getStandaloneConfig(), clientConfiguration);
119+
LettuceClientConfiguration clientConfiguration = getLettuceClientConfiguration(
120+
clientConfigurationBuilderCustomizers, clientOptionsBuilderCustomizers, clientResources,
121+
getProperties().getLettuce().getPool());
122+
return switch (this.mode) {
123+
case STANDALONE -> new LettuceConnectionFactory(getStandaloneConfig(), clientConfiguration);
124+
case CLUSTER -> new LettuceConnectionFactory(getClusterConfiguration(), clientConfiguration);
125+
case SENTINEL -> new LettuceConnectionFactory(getSentinelConfig(), clientConfiguration);
126+
};
132127
}
133128

134129
private LettuceClientConfiguration getLettuceClientConfiguration(
135130
ObjectProvider<LettuceClientConfigurationBuilderCustomizer> clientConfigurationBuilderCustomizers,
136131
ObjectProvider<LettuceClientOptionsBuilderCustomizer> clientOptionsBuilderCustomizers,
137132
ClientResources clientResources, Pool pool) {
138133
LettuceClientConfigurationBuilder builder = createBuilder(pool);
139-
applyProperties(builder);
134+
SslBundle sslBundle = getSslBundle();
135+
applyProperties(builder, sslBundle);
140136
if (StringUtils.hasText(getProperties().getUrl())) {
141137
customizeConfigurationFromUrl(builder);
142138
}
143-
builder.clientOptions(createClientOptions(clientOptionsBuilderCustomizers));
139+
builder.clientOptions(createClientOptions(clientOptionsBuilderCustomizers, sslBundle));
144140
builder.clientResources(clientResources);
145141
clientConfigurationBuilderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
146142
return builder.build();
@@ -153,8 +149,8 @@ private LettuceClientConfigurationBuilder createBuilder(Pool pool) {
153149
return LettuceClientConfiguration.builder();
154150
}
155151

156-
private void applyProperties(LettuceClientConfiguration.LettuceClientConfigurationBuilder builder) {
157-
if (isSslEnabled()) {
152+
private void applyProperties(LettuceClientConfigurationBuilder builder, SslBundle sslBundle) {
153+
if (sslBundle != null) {
158154
builder.useSsl();
159155
}
160156
if (getProperties().getTimeout() != null) {
@@ -195,14 +191,14 @@ private String getCanonicalReadFromName(String name) {
195191
}
196192

197193
private ClientOptions createClientOptions(
198-
ObjectProvider<LettuceClientOptionsBuilderCustomizer> clientConfigurationBuilderCustomizers) {
194+
ObjectProvider<LettuceClientOptionsBuilderCustomizer> clientConfigurationBuilderCustomizers,
195+
SslBundle sslBundle) {
199196
ClientOptions.Builder builder = initializeClientOptionsBuilder();
200197
Duration connectTimeout = getProperties().getConnectTimeout();
201198
if (connectTimeout != null) {
202199
builder.socketOptions(SocketOptions.builder().connectTimeout(connectTimeout).build());
203200
}
204-
if (isSslEnabled() && getProperties().getSsl().getBundle() != null) {
205-
SslBundle sslBundle = getSslBundles().getBundle(getProperties().getSsl().getBundle());
201+
if (sslBundle != null) {
206202
io.lettuce.core.SslOptions.Builder sslOptionsBuilder = io.lettuce.core.SslOptions.builder();
207203
sslOptionsBuilder.keyManager(sslBundle.getManagers().getKeyManagerFactory());
208204
sslOptionsBuilder.trustManager(sslBundle.getManagers().getTrustManagerFactory());

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

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@
1818

1919
import java.util.List;
2020

21+
import org.springframework.boot.ssl.SslBundle;
22+
import org.springframework.boot.ssl.SslBundles;
23+
import org.springframework.util.Assert;
24+
import org.springframework.util.StringUtils;
25+
2126
/**
2227
* Adapts {@link RedisProperties} to {@link RedisConnectionDetails}.
2328
*
@@ -32,8 +37,11 @@ class PropertiesRedisConnectionDetails implements RedisConnectionDetails {
3237

3338
private final RedisProperties properties;
3439

35-
PropertiesRedisConnectionDetails(RedisProperties properties) {
40+
private final SslBundles sslBundles;
41+
42+
PropertiesRedisConnectionDetails(RedisProperties properties, SslBundles sslBundles) {
3643
this.properties = properties;
44+
this.sslBundles = sslBundles;
3745
}
3846

3947
@Override
@@ -52,8 +60,21 @@ public String getPassword() {
5260
public Standalone getStandalone() {
5361
RedisUrl redisUrl = getRedisUrl();
5462
return (redisUrl != null)
55-
? Standalone.of(redisUrl.uri().getHost(), redisUrl.uri().getPort(), redisUrl.database())
56-
: Standalone.of(this.properties.getHost(), this.properties.getPort(), this.properties.getDatabase());
63+
? Standalone.of(redisUrl.uri().getHost(), redisUrl.uri().getPort(), redisUrl.database(), getSslBundle())
64+
: Standalone.of(this.properties.getHost(), this.properties.getPort(), this.properties.getDatabase(),
65+
getSslBundle());
66+
}
67+
68+
private SslBundle getSslBundle() {
69+
if (!this.properties.getSsl().isEnabled()) {
70+
return null;
71+
}
72+
String bundleName = this.properties.getSsl().getBundle();
73+
if (StringUtils.hasLength(bundleName)) {
74+
Assert.notNull(this.sslBundles, "SSL bundle name has been set but no SSL bundles found in context");
75+
return this.sslBundles.getBundle(bundleName);
76+
}
77+
return SslBundle.systemDefault();
5778
}
5879

5980
@Override
@@ -65,8 +86,7 @@ public Sentinel getSentinel() {
6586
@Override
6687
public Cluster getCluster() {
6788
RedisProperties.Cluster cluster = this.properties.getCluster();
68-
List<Node> nodes = (cluster != null) ? asNodes(cluster.getNodes()) : null;
69-
return (nodes != null) ? () -> nodes : null;
89+
return (cluster != null) ? new PropertiesCluster(cluster) : null;
7090
}
7191

7292
private RedisUrl getRedisUrl() {
@@ -84,6 +104,29 @@ private Node asNode(String node) {
84104
return new Node(host, port);
85105
}
86106

107+
/**
108+
* {@link Cluster} implementation backed by properties.
109+
*/
110+
private class PropertiesCluster implements Cluster {
111+
112+
private final List<Node> nodes;
113+
114+
PropertiesCluster(RedisProperties.Cluster properties) {
115+
this.nodes = asNodes(properties.getNodes());
116+
}
117+
118+
@Override
119+
public List<Node> getNodes() {
120+
return this.nodes;
121+
}
122+
123+
@Override
124+
public SslBundle getSslBundle() {
125+
return PropertiesRedisConnectionDetails.this.getSslBundle();
126+
}
127+
128+
}
129+
87130
/**
88131
* {@link Sentinel} implementation backed by properties.
89132
*/
@@ -123,6 +166,11 @@ public String getPassword() {
123166
return this.properties.getPassword();
124167
}
125168

169+
@Override
170+
public SslBundle getSslBundle() {
171+
return PropertiesRedisConnectionDetails.this.getSslBundle();
172+
}
173+
126174
}
127175

128176
}

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,12 +16,14 @@
1616

1717
package org.springframework.boot.autoconfigure.data.redis;
1818

19+
import org.springframework.beans.factory.ObjectProvider;
1920
import org.springframework.boot.autoconfigure.AutoConfiguration;
2021
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
2122
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2223
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
2324
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
2425
import org.springframework.boot.context.properties.EnableConfigurationProperties;
26+
import org.springframework.boot.ssl.SslBundles;
2527
import org.springframework.context.annotation.Bean;
2628
import org.springframework.context.annotation.Import;
2729
import org.springframework.data.redis.connection.RedisConnectionFactory;
@@ -51,8 +53,9 @@ public class RedisAutoConfiguration {
5153

5254
@Bean
5355
@ConditionalOnMissingBean(RedisConnectionDetails.class)
54-
PropertiesRedisConnectionDetails redisConnectionDetails(RedisProperties properties) {
55-
return new PropertiesRedisConnectionDetails(properties);
56+
PropertiesRedisConnectionDetails redisConnectionDetails(RedisProperties properties,
57+
ObjectProvider<SslBundles> sslBundles) {
58+
return new PropertiesRedisConnectionDetails(properties, sslBundles.getIfAvailable());
5659
}
5760

5861
@Bean

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.springframework.boot.autoconfigure.data.redis.RedisConnectionDetails.Node;
2525
import org.springframework.boot.autoconfigure.data.redis.RedisConnectionDetails.Sentinel;
2626
import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Pool;
27+
import org.springframework.boot.ssl.SslBundle;
2728
import org.springframework.boot.ssl.SslBundles;
2829
import org.springframework.data.redis.connection.RedisClusterConfiguration;
2930
import org.springframework.data.redis.connection.RedisNode;
@@ -62,6 +63,8 @@ abstract class RedisConnectionConfiguration {
6263

6364
private final SslBundles sslBundles;
6465

66+
protected final Mode mode;
67+
6568
protected RedisConnectionConfiguration(RedisProperties properties, RedisConnectionDetails connectionDetails,
6669
ObjectProvider<RedisStandaloneConfiguration> standaloneConfigurationProvider,
6770
ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider,
@@ -73,6 +76,7 @@ protected RedisConnectionConfiguration(RedisProperties properties, RedisConnecti
7376
this.clusterConfiguration = clusterConfigurationProvider.getIfAvailable();
7477
this.connectionDetails = connectionDetails;
7578
this.sslBundles = sslBundles.getIfAvailable();
79+
this.mode = determineMode();
7680
}
7781

7882
protected final RedisStandaloneConfiguration getStandaloneConfig() {
@@ -153,6 +157,17 @@ protected final SslBundles getSslBundles() {
153157
return this.sslBundles;
154158
}
155159

160+
protected SslBundle getSslBundle() {
161+
return switch (this.mode) {
162+
case STANDALONE -> (this.connectionDetails.getStandalone() != null)
163+
? this.connectionDetails.getStandalone().getSslBundle() : null;
164+
case CLUSTER -> (this.connectionDetails.getCluster() != null)
165+
? this.connectionDetails.getCluster().getSslBundle() : null;
166+
case SENTINEL -> (this.connectionDetails.getSentinel() != null)
167+
? this.connectionDetails.getSentinel().getSslBundle() : null;
168+
};
169+
}
170+
156171
protected final boolean isSslEnabled() {
157172
return getProperties().getSsl().isEnabled();
158173
}
@@ -178,4 +193,20 @@ protected final RedisConnectionDetails getConnectionDetails() {
178193
return this.connectionDetails;
179194
}
180195

196+
private Mode determineMode() {
197+
if (getSentinelConfig() != null) {
198+
return Mode.SENTINEL;
199+
}
200+
if (getClusterConfiguration() != null) {
201+
return Mode.CLUSTER;
202+
}
203+
return Mode.STANDALONE;
204+
}
205+
206+
enum Mode {
207+
208+
STANDALONE, CLUSTER, SENTINEL
209+
210+
}
211+
181212
}

0 commit comments

Comments
 (0)