Make Redis to use SSL in SpringBoot

March 11, 2024

Redis in SpringBoot

The post assume you are use spring-boot-starter-data-redis to use Redis in SpringBoot Application.
There has two lib to implements redis in SpringBoot, which are Lettuce (default) and Jedis.
The first thing you need to do is set spring.redis.ssl to true in application.yaml. (or properties).
WARN
You also need set custom keystore path and password if you want to fully follow the below lib configuration in next section.
The post will use PKCS12-formatted keystore, JKS-formatted keystore also can work fine.
spring.redis.keyStore = "classpath:server.p12"
spring.redis.keyStorePass = "password of pkcs12"
spring.redis.keyStorePass = "PKCS12"plaintext
Then follow the right section to see how to config SSL in 3rd party library.

Lettuce

RedisConfig.java
@Configuration
public class RedisConfig {

    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private int port;
    @Value("${spring.redis.ssl}")
    private boolean ssl;
    @Value("${spring.redis.password}")
    private String password;
    @Value("${spring.redis.database}")
    private int database;
    @Value("${spring.redis.timeout}")
    private int timeout;
    @Value("${spring.redis.keyStore}")
    private String keyStorePath;
    @Value("${spring.redis.keyStorePass}")
    private String keyStorePass;

    @Resource
    private ResourceLoader resourceLoader;

    @Bean
    public LettuceConnectionFactory redisConnectionFactory() throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException {
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setHostName(host);
        redisStandaloneConfiguration.setPort(port);
        redisStandaloneConfiguration.setPassword(password);
        redisStandaloneConfiguration.setDatabase(database);
        LettuceClientConfiguration.LettuceClientConfigurationBuilder lettuceClientConfigurationBuilder = LettuceClientConfiguration.builder();
        if (ssl) {
            File keyStore = resourceLoader.getResource(keyStorePath).getFile();
            SslOptions sslOptions = SslOptions.builder()
                    .jdkSslProvider()
                    .keystore(keyStore, keyStorePass.toCharArray())
                    .truststore(keyStore, keyStorePass)
                    .build();
            ClientOptions clientOptions = ClientOptions
                    .builder()
                    .sslOptions(sslOptions)
                    .protocolVersion(ProtocolVersion.RESP3)
                    .build();
            lettuceClientConfigurationBuilder
                    .clientOptions(clientOptions)
                    .useSsl()
                    .disablePeerVerification(); // if you don't set SAN, you need this
        }
        LettuceClientConfiguration lettuceClientConfiguration = lettuceClientConfigurationBuilder.build();
        return new LettuceConnectionFactory(redisStandaloneConfiguration, lettuceClientConfiguration);
    }java

Jedis

If you don't want to use default Lettuce implementation, you can update POM to use Jedis.
pom.xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <exclusions>
        <exclusion>
            <groupId>io.lettuce</groupId>
            <artifactId>lettuce-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>5.0.0</version>
</dependency>xml
RedisConfig.java
@Configuration
public class RedisConfig {

    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private int port;
    @Value("${spring.redis.ssl}")
    private boolean ssl;
    @Value("${spring.redis.password}")
    private String password;
    @Value("${spring.redis.database}")
    private int database;
    @Value("${spring.redis.timeout}")
    private int timeout;
    @Value("${spring.redis.keyStore}")
    private String keyStorePath;
    @Value("${spring.redis.keyStorePass}")
    private String keyStorePass;
    @Value("${spring.redis.keyStoreType}")
    private String keyStoreType;

    // @Value("${spring.redis.jedis.pool.max-idle}")
    // private int maxIdle;
    // @Value("${spring.redis.jedis.pool.min-idle}")
    // private int minIdle;
    // @Value("${spring.redis.jedis.pool.max-wait}")
    // private int maxWait;
    // @Value("${spring.redis.jedis.pool.max-active}")
    // private int maxActive;

    @Resource
    private ResourceLoader resourceLoader;


    @Bean
    public JedisConnectionFactory redisConnectionFactory() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException, KeyManagementException {
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setHostName(host);
        redisStandaloneConfiguration.setPort(port);
        redisStandaloneConfiguration.setPassword(password);
        redisStandaloneConfiguration.setDatabase(database);

        JedisClientConfiguration.JedisClientConfigurationBuilder jedisClientConfigurationBuilder = JedisClientConfiguration.builder();
        if (ssl) {
            KeyStore keyStore = KeyStore.getInstance(keyStoreType);
            File keyStoreFile = resourceLoader.getResource(keyStorePath).getFile();
            keyStore.load(new FileInputStream(keyStoreFile), keyStorePass.toCharArray());
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(keyStore, keyStorePass.toCharArray());
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keyStore);
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
            jedisClientConfigurationBuilder
                    .connectTimeout(Duration.ofMillis(timeout))
                    .useSsl()
                    .sslSocketFactory(sslContext.getSocketFactory());
        }
        JedisClientConfiguration jedisClientConfiguration = jedisClientConfigurationBuilder.build();
        return new JedisConnectionFactory(redisStandaloneConfiguration, jedisClientConfiguration);
    }

//     @Bean
//     public GenericObjectPoolConfig genericObjectPoolConfig() {
//         GenericObjectPoolConfig<Object> genericObjectPoolConfig = new GenericObjectPoolConfig<>();
//         genericObjectPoolConfig.setMaxIdle(maxIdle);
//         genericObjectPoolConfig.setMinIdle(minIdle);
//         genericObjectPoolConfig.setMaxWaitMillis(maxWait);
// //        genericObjectPoolConfig.setMaxTotal(maxActive);
//         return genericObjectPoolConfig;
//     }
}java
The End