全部产品
Search
文档中心

Tair (Redis® OSS-Compatible):Aktifkan koneksi terenkripsi TLS (SSL) ke sebuah instans

更新时间:Nov 10, 2025

Saat menggunakan klien untuk menghubungkan ke instans Tair (Redis OSS-compatible), Anda dapat mengaktifkan enkripsi TLS (SSL) untuk meningkatkan keamanan tautan data dan memastikan integritas data. Gunakan klien apa pun yang kompatibel dengan protokol Redis. Topik ini menyediakan contoh kode untuk menghubungkan ke instans menggunakan klien umum.

Prasyarat

  • Enkripsi TLS (SSL) harus diaktifkan untuk instans Tair Anda. Untuk informasi lebih lanjut, lihat Aktifkan Enkripsi TLS.

  • Klien harus di-hosting pada Instance ECS (Elastic Compute Service) yang berada dalam virtual private cloud (VPC) yang sama dengan instans Tair.

Catatan

Jika akses tanpa kata sandi melalui VPC diaktifkan untuk instans, klien dalam VPC yang sama dapat terhubung ke instans tanpa kata sandi.

Persiapan

  1. Tambahkan alamat IP internal dari Instance ECS yang meng-hosting klien ke daftar putih instans Tair. Untuk informasi lebih lanjut, lihat Konfigurasi Daftar Putih.

  2. Peroleh informasi berikut untuk dikonfigurasikan dalam kode program klien Anda:

    Informasi yang harus diperoleh

    Cara memperoleh

    Titik akhir instans

    Pergi ke halaman Instans, pilih wilayah di bilah navigasi atas, lalu klik ID instans target. Di bagian Connection Information, Anda dapat melihat titik akhir dan port untuk jenis koneksi yang berbeda.

    Catatan

    Instans mendukung beberapa jenis titik akhir. Kami sarankan Anda menggunakan titik akhir VPC untuk keamanan yang lebih tinggi dan latensi jaringan yang lebih rendah. Untuk informasi lebih lanjut, lihat Lihat titik akhir.

    Port

    Port default adalah 6379. Anda juga dapat menyesuaikan port. Untuk informasi lebih lanjut, lihat Ubah titik akhir atau port.

    Akun instans (tidak diperlukan untuk beberapa klien)

    Secara default, sebuah instans memiliki akun bernama setelah ID instans, seperti r-bp10noxlhcoim2****. Anda juga dapat membuat akun baru dan memberikan izin kepadanya. Untuk informasi lebih lanjut, lihat Buat dan kelola akun.

    Kata sandi akun

    Format kata sandi bervariasi berdasarkan akun yang dipilih:

    • Akun default (akun bernama setelah ID instans): Masukkan hanya kata sandi.

    • Akun yang baru dibuat: Format kata sandi adalah <user>:<password>. Sebagai contoh, jika akun kustom adalah testaccount dan kata sandinya adalah Rp829dlwa, Anda harus memasukkan testaccount:Rp829dlwa.

    Catatan
    • Jika Anda menggunakan alat manajemen database pihak ketiga, seperti RDM, untuk menghubungkan ke instans, Anda harus memasukkan kata sandi dalam format user:password di bidang kata sandi.

    • Jika Anda lupa kata sandi Anda, Anda dapat menyetel ulang. Untuk informasi lebih lanjut, lihat Ubah atau setel ulang kata sandi.

  3. Unduh sertifikat otoritas sertifikasi (CA). Untuk informasi lebih lanjut, lihat Aktifkan Enkripsi TLS.

Proksi mode koneksi

Mode ini berlaku untuk instans yang menggunakan arsitektur standar, arsitektur kluster dengan mode proxy, atau arsitektur pemisahan baca/tulis. Perluas bagian berikut untuk melihat contoh kode.

redis-cli

Saat mengompilasi Redis, tentukan BUILD_TLS=yes untuk mengaktifkan koneksi TLS di redis-cli.

  1. Masuk ke Instance ECS dan jalankan perintah berikut untuk mengunduh, menginstal, dan mengompilasi redis-cli.

    sudo yum -y install openssl-devel gcc    # Instal dependensi gcc.
    wget https://download.redis.io/releases/redis-7.2.0.tar.gz
    tar xzf redis-7.2.0.tar.gz
    cd redis-7.2.0&&make BUILD_TLS=yes

    Topik ini menggunakan Redis 7.2.0 sebagai contoh. Anda juga dapat menginstal versi lainnya. Proses kompilasi dan instalasi biasanya memakan waktu 2 hingga 3 menit.

  2. Jalankan perintah berikut di jendela baris perintah untuk menghubungkan ke instans.

    ./src/redis-cli -h r-bp14joyeihew30****.redis.rds.aliyuncs.com -p 6379 --tls --cacert ./ApsaraDB-CA-Chain.pem

    Setelah parameter cacert, tentukan jalur ke sertifikat CA.

  3. Jalankan perintah berikut untuk menyelesaikan otentikasi kata sandi.

    AUTH password

    Tanggapan OK menunjukkan bahwa Anda telah terhubung ke instans.

Java

Contoh ini menggunakan Jedis versi 3.6.0. Gunakan versi terbaru.

import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.SecureRandom;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

public class JedisSSLTest {
    private static SSLSocketFactory createTrustStoreSSLSocketFactory(String jksFile) throws Exception {
        KeyStore trustStore = KeyStore.getInstance("jks");
        InputStream inputStream = null;
        try {
            inputStream = new FileInputStream(jksFile);
            trustStore.load(inputStream, null);
        } finally {
            inputStream.close();
        }

        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("PKIX");
        trustManagerFactory.init(trustStore);
        TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();

        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, trustManagers, new SecureRandom());
        return sslContext.getSocketFactory();
    }

    public static void main(String[] args) throws Exception {
        // ApsaraDB-CA-Chain.jks adalah nama file sertifikat.
        final SSLSocketFactory sslSocketFactory = createTrustStoreSSLSocketFactory("ApsaraDB-CA-Chain.jks");
        // Pengaturan kolam koneksi adalah titik akhir instans, nomor port, pengaturan timeout, dan kata sandi.
        JedisPool pool = new JedisPool(new GenericObjectPoolConfig(), "r-bp1zxszhcgatnx****.redis.rds.aliyuncs.com",
            6379, 2000, "redistest:Pas***23", 0, true, sslSocketFactory, null, null);

        try (Jedis jedis = pool.getResource()) {
            jedis.set("key", "value");
            System.out.println(jedis.get("key"));
        }
    }
}

Python

Contoh ini menggunakan klien redis-py. Gunakan versi terbaru.

Kolam koneksi

#!/bin/python
import redis

# Atur kolam koneksi. Ganti nilai host, port, dan password dengan titik akhir instans, nomor port, dan kata sandi.
# ApsaraDB-CA-Chain.pem adalah nama file sertifikat.
pool = redis.ConnectionPool(connection_class=redis.connection.SSLConnection, max_connections=100,
                            host="r-bp1zxszhcgatnx****.redis.rds.aliyuncs.com", port=6379, password="redistest:Pas***23",
                            ssl_cert_reqs=True, ssl_ca_certs="ApsaraDB-CA-Chain.pem")
client = redis.Redis(connection_pool=pool)
client.set("hi", "redis")
print client.get("hi")

Koneksi standar

#!/bin/python
import redis

# Atur informasi koneksi. Ganti nilai host, port, dan password dengan titik akhir instans, nomor port, dan kata sandi.
# ApsaraDB-CA-Chain.pem adalah nama file sertifikat.
client = redis.Redis(host="r-bp1zxszhcgatnx****.redis.rds.aliyuncs.com", port=6379,
                     password="redistest:Test1234", ssl=True,
                    ssl_cert_reqs="required", ssl_ca_certs="ApsaraDB-CA-Chain.pem")

client.set("hello", "world")
print client.get("hello")

PHP

Contoh ini menggunakan klien predis. Gunakan versi terbaru. Jika Anda menggunakan klien phpredis, lihat phpredis/phpredis#1600 untuk contoh koneksi.

<?php

require __DIR__.'/predis/autoload.php';

/* Atur informasi koneksi. Ganti nilai host, port, dan password dengan titik akhir instans, nomor port, dan kata sandi.
ApsaraDB-CA-Chain.pem adalah nama file sertifikat. */
$client = new Predis\Client([
    'scheme' => 'tls',
    'host'   => 'r-bp1zxszhcgatnx****.redis.rds.aliyuncs.com',
    'port'   => 6379,
    'password' => 'redistest:Pas***23',
    'ssl'    => ['cafile' => 'ApsaraDB-CA-Chain.pem', 'verify_peer' => true],
]);
/* Ganti titik akhir dan port dalam kode berikut. */
//$client = new Predis\Client('tls://r-bp1zxszhcgatnx****.redis.rds.aliyuncs.com:6379?ssl[cafile]=ApsaraDB-CA-Chain.pem&ssl[verify_peer]=1');

$client->set("hello", "world");
print $client->get("hello")."\n";

?>

C#

Contoh ini menggunakan klien StackExchange.Redis. Gunakan versi terbaru.

using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using StackExchange.Redis;

namespace SSLTest
{
    class Program
    {
        private static bool CheckServerCertificate(object sender, X509Certificate certificate,
            X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            var ca = new X509Certificate2(
                "/your path/ApsaraDB-CA-Chain/ApsaraDB-CA-Chain.pem");
            return chain.ChainElements
                .Cast<X509ChainElement>()
                .Any(x => x.Certificate.Thumbprint == ca.Thumbprint);
        }

        static void Main(string[] args)
        {
          // Atur informasi koneksi. Ganti nilai host, port, dan password dengan titik akhir instans, nomor port, dan kata sandi.
          // ApsaraDB-CA-Chain.pem adalah nama file sertifikat.
            ConfigurationOptions config = new ConfigurationOptions()
            {
                EndPoints = {"r-bp10q23zyfriodu*****.redis.rds.aliyuncs.com:6379"},
                Password = "redistest:Pas***23",
                Ssl = true,
            };

            config.CertificateValidation += CheckServerCertificate;
            using (var conn = ConnectionMultiplexer.Connect(config))
            {
                Console.WriteLine("connected");
                var db = conn.GetDatabase();
                db.StringSet("hello", "world");
                Console.WriteLine(db.StringGet("hello"));
            }
        }
    }
}

Spring Data Redis

Contoh ini menggunakan Spring Data Redis versi 2.7.12 (untuk Java 1.8). Gunakan versi terbaru.

@Configuration
public class RedisConfig {
    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        // Simpan konfigurasi sertifikat TLS dalam file properti.
        String host = "r-bp1zxszhcgatnx****.redis.rds.aliyuncs.com";
        int port = 6379;
        String password = "Pas***23";
        String trustStoreFilePath = "/path/to/ApsaraDB-CA-Chain.jks";

        ClientOptions clientOptions = ClientOptions.builder().sslOptions(
            SslOptions.builder().jdkSslProvider().truststore(new File(trustStoreFilePath)).build()).build();
        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
        config.setHostName(host);
        config.setPort(port);
        config.setPassword(password);
        LettuceClientConfiguration lettuceClientConfiguration = LettuceClientConfiguration.builder()
            .clientOptions(clientOptions)
            .useSsl().build();
        return new LettuceConnectionFactory(config, lettuceClientConfiguration);
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
    }
}

Lettuce

Contoh ini menggunakan Lettuce versi 6.2.4.RELEASE. Gunakan versi terbaru.

public class SSLExample {
    public static void main(String[] args) throws Exception {
        String host = "r-bp1zxszhcgatnx****.redis.rds.aliyuncs.com";
        int port = 6379;
        String password = "Pas***23";
        String trustStoreFilePath = "/path/to/ApsaraDB-CA-Chain.jks";

        RedisURI uri = RedisURI.builder()
            .withHost(host)
            .withPort(port)
            .withPassword(password.toCharArray())
            .withSsl(true).build();

        SslOptions sslOptions = SslOptions.builder()
            .jdkSslProvider()
            .truststore(new File(trustStoreFilePath)).build();

        ClientOptions clientOptions = ClientOptions.builder()
            .sslOptions(sslOptions).build();
        RedisClient client = RedisClient.create(uri);
        client.setOptions(clientOptions);

        RedisCommands<String, String> sync = client.connect().sync();
        System.out.println(sync.set("key", "value"));
        System.out.println(sync.get("key"));

    }
}

Go

Contoh ini menggunakan klien go-redis v9.5.1. Gunakan v9.0 atau lebih baru.

package main

import (
	"context"
	"fmt"
	"io/ioutil"
	"crypto/tls"
	"crypto/x509"
	"github.com/redis/go-redis/v9"
)

var ctx = context.Background()

func main() {
        caCert, err := ioutil.ReadFile("/root/ApsaraDB-CA-Chain.pem")
	if err != nil {
		fmt.Println("Error loading CA certificate:", err)
		return
	}

	caCertPool := x509.NewCertPool()
	caCertPool.AppendCertsFromPEM(caCert)

	tlsConfig := &tls.Config{
		RootCAs:            caCertPool,
		InsecureSkipVerify: true, // Not actually skipping, we check the cert in VerifyPeerCertificate
		VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
			// Code copy/pasted and adapted from
			// https://github.com/golang/go/blob/81555cb4f3521b53f9de4ce15f64b77cc9df61b9/src/crypto/tls/handshake_client.go#L327-L344, but adapted to skip the hostname verification.
			// See https://github.com/golang/go/issues/21971#issuecomment-412836078.

			// If this is the first handshake on a connection, process and
			// (optionally) verify the server's certificates.
			certs := make([]*x509.Certificate, len(rawCerts))
			for i, asn1Data := range rawCerts {
				cert, err := x509.ParseCertificate(asn1Data)
				if err != nil {
					panic(err)
				}
				certs[i] = cert
			}

			opts := x509.VerifyOptions{
				Roots:         caCertPool,
				DNSName:       "", // <- skip hostname verification
				Intermediates: x509.NewCertPool(),
			}

			for i, cert := range certs {
				if i == 0 {
					continue
				}
				opts.Intermediates.AddCert(cert)
			}
			_, err := certs[0].Verify(opts)
			return err
		},
	}

	rdb := redis.NewClient(&redis.Options{
		Addr:    "r-2zee50zxi5iiqm****.redis.rds-aliyun.rds.aliyuncs.com:6379",
		Username: "default",
		Password: "Pas***23",
		TLSConfig: tlsConfig,
	})

	err = rdb.Set(ctx, "key", "value", 0).Err()
	if err != nil {
		panic(err)
	}

	val, err := rdb.Get(ctx, "key").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("key:", val)
}

Mode koneksi langsung

Mode ini hanya berlaku untuk instans yang menggunakan arsitektur kluster dengan mode koneksi langsung. Perluas bagian berikut untuk melihat contoh kode.

redis-cli

Saat mengompilasi Redis, tentukan BUILD_TLS=yes untuk mengaktifkan koneksi TLS di redis-cli.

  1. Masuk ke Instance ECS dan jalankan perintah berikut untuk mengunduh, menginstal, dan mengompilasi redis-cli.

    sudo yum -y install openssl-devel gcc    # Instal dependensi gcc.
    wget https://download.redis.io/releases/redis-7.2.0.tar.gz
    tar xzf redis-7.2.0.tar.gz
    cd redis-7.2.0&&make BUILD_TLS=yes

    Topik ini menggunakan Redis 7.2.0 sebagai contoh. Anda juga dapat menginstal versi lainnya. Proses kompilasi dan instalasi biasanya memakan waktu 2 hingga 3 menit.

  2. Jalankan perintah berikut di jendela baris perintah untuk menghubungkan ke instans.

    ./src/redis-cli -h r-bp14joyeihew30****.redis.rds.aliyuncs.com -p 6379 -c --tls --cacert ./ApsaraDB-CA-Chain.pem

    Setelah parameter cacert, tentukan jalur ke sertifikat CA.

  3. Jalankan perintah berikut untuk menyelesaikan otentikasi kata sandi.

    AUTH password

    Tanggapan OK menunjukkan bahwa Anda telah terhubung ke instans.

Java

Contoh ini menggunakan Jedis versi 4.3.0. Gunakan versi terbaru.

import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.HashSet;
import java.util.Set;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import redis.clients.jedis.ConnectionPoolConfig;
import redis.clients.jedis.DefaultJedisClientConfig;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;

public class JedisClusterTSL {
    private static final int DEFAULT_TIMEOUT = 2000;
    private static final int DEFAULT_REDIRECTIONS = 5;
    private static final ConnectionPoolConfig jedisPoolConfig = new ConnectionPoolConfig();

    private static SSLSocketFactory createTrustStoreSSLSocketFactory(String jksFile) throws Exception {
        KeyStore trustStore = KeyStore.getInstance("jks");
        InputStream inputStream = null;
        try {
            inputStream = new FileInputStream(jksFile);
            trustStore.load(inputStream, null);
        } finally {
            inputStream.close();
        }

        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("PKIX");
        trustManagerFactory.init(trustStore);
        TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();

        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, trustManagers, new SecureRandom());
        return sslContext.getSocketFactory();
    }

    public static void main(String args[]) throws Exception{
        // Jumlah maksimum koneksi idle. Dalam mode koneksi langsung, klien terhubung langsung ke shard database. Pastikan bahwa nilai (Jumlah mesin bisnis × MaxTotal) lebih kecil dari jumlah maksimum koneksi untuk satu shard database.
        jedisPoolConfig.setMaxTotal(30);
        // Jumlah maksimum koneksi idle. Atur nilai ini sesuai kebutuhan.
        jedisPoolConfig.setMaxIdle(30);
        jedisPoolConfig.setMinIdle(15);

        // Titik akhir koneksi langsung.
        int port = 6379;
        String host = "r-2zee50zxi5iiq****.redis.rds-aliyun.rds.aliyuncs.com";
        String user = "default";
        String password = "Pas***23";

        final SSLSocketFactory sslSocketFactory = createTrustStoreSSLSocketFactory("/root/ApsaraDB-CA-Chain.jks");
        DefaultJedisClientConfig jedisClientConfig = DefaultJedisClientConfig.builder().connectionTimeoutMillis(DEFAULT_TIMEOUT)
            .socketTimeoutMillis(DEFAULT_TIMEOUT)
            .user(user).password(password)
            .ssl(true)
            .sslSocketFactory(sslSocketFactory).build();

        Set<HostAndPort> jedisClusterNode = new HashSet<HostAndPort>();
        jedisClusterNode.add(new HostAndPort(host, port));
        JedisCluster jc = new JedisCluster(jedisClusterNode, jedisClientConfig, DEFAULT_REDIRECTIONS, jedisPoolConfig);

        System.out.println(jc.set("key", "value"));
        System.out.println(jc.get("key"));

        jc.close();     // Saat aplikasi keluar dan sumber daya perlu dihancurkan, panggil metode ini. Metode ini memutuskan koneksi klien dan melepaskan sumber daya.
    }
}

Python

Contoh ini menggunakan klien redis-py versi 4.3.6 (untuk Python 3.6). Gunakan versi terbaru.

#!/usr/bin/env python
from redis.cluster import RedisCluster

# Ganti nilai host dan port dengan titik akhir instans dan nomor port.
host = 'r-2zee50zxi5iiqm****.redis.rds-aliyun.rds.aliyuncs.com'
port = 6379
# Ganti nilai user dan pwd dengan akun instans dan kata sandi.
user = 'default'
pwd = 'Pas***23'

rc = RedisCluster(host=host, port=port, username=user, password=pwd, ssl=True, ssl_ca_certs="/root/ApsaraDB-CA-Chain.pem")
# Setelah koneksi dibuat, Anda dapat melakukan operasi database. Kode berikut memberikan contoh penggunaan SET dan GET.
rc.set('foo', 'bar')
print(rc.get('foo'))

PHP

Contoh ini menggunakan klien phpredis versi 5.3.7. Gunakan versi terbaru.

<?php
 // Titik akhir koneksi langsung dan port.
 $array = ['r-2zee50zxi5iiqm****.redis.rds-aliyun.rds.aliyuncs.com:6379'];
 // Kata sandi koneksi.
 $pwd = "Pas***23";
 // Informasi koneksi TLS.
 $tls = ["verify_peer" => false, "verify_peer_name" => false];
 // Hubungkan ke kluster dengan kata sandi.
 $obj_cluster = new RedisCluster(NULL, $array, 1.5, 1.5, true, $pwd, $tls);

 // Keluarkan hasil koneksi.
 var_dump($obj_cluster);

 if ($obj_cluster->set("foo", "bar") == false) {
     die($obj_cluster->getLastError());
 }
 $value = $obj_cluster->get("foo");
 echo $value;
 echo "\n";
 ?>

C#

Contoh ini menggunakan klien StackExchange.Redis. Gunakan versi terbaru.

using StackExchange.Redis;
using System;
using System.Linq;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;

namespace TairClient
{
    class Program
    {
        static void Main()
        {
            // Titik akhir koneksi langsung.
            const string Host = "r-2zee50zxi5iiqm****.redis.rds-aliyun.rds.aliyuncs.com";
            const int Port = 6379;
            Console.WriteLine("menghubungkan...");
            var config = new ConfigurationOptions
            {
                EndPoints = { { Host, Port } },
                Ssl = true,
                Password = "Pas***23",
            };
            config.CertificateValidation += (sender, cert, chain, errors) =>
            {
                if (errors == SslPolicyErrors.RemoteCertificateChainErrors || errors ==  SslPolicyErrors.RemoteCertificateNameMismatch)
                {
                    return true;
                }
                var caCert = LoadCertificateFromPem("/root/ApsaraDB-CA-Chain.pem");

                var isCertIssuedByTrustedCA = chain.ChainElements
                    .Cast<X509ChainElement>()
                    .Any(x => x.Certificate.Thumbprint.Equals(caCert.Thumbprint, StringComparison.OrdinalIgnoreCase));

                // Sesuaikan logika validasi lainnya...
                return isCertIssuedByTrustedCA;
            };

            using (var conn = ConnectionMultiplexer.Connect(config))
            {
                Console.WriteLine("terhubung");
                var db = conn.GetDatabase();
                db.StringSet("hello", "world");
                Console.WriteLine(db.StringGet("hello")); // menulis: world
            }
        }

        private static X509Certificate2 LoadCertificateFromPem(string pemFilePath)
        {
            // Gunakan metode statis X509Certificate2 untuk memuat sertifikat langsung dari konten PEM.
            X509Certificate2 cert = X509Certificate2.CreateFromPem(File.ReadAllText(pemFilePath));
            return cert;
        }

    }
}

Spring Data Redis

Contoh ini menggunakan Spring Data Redis versi 2.7.5 (untuk Java 1.8). Gunakan versi terbaru.

Dengan Jedis (disarankan)

import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.List;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import redis.clients.jedis.JedisPoolConfig;

@Configuration
public class RedisConfigJedis {
    private static SSLSocketFactory createTrustStoreSSLSocketFactory(String jksFile) throws Exception {
        KeyStore trustStore = KeyStore.getInstance("jks");
        InputStream inputStream = null;
        try {
            inputStream = new FileInputStream(jksFile);
            trustStore.load(inputStream, null);
        } finally {
            inputStream.close();
        }

        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("PKIX");
        trustManagerFactory.init(trustStore);
        TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();

        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, trustManagers, new SecureRandom());
        return sslContext.getSocketFactory();
    }

    @Bean
    public RedisConnectionFactory redisConnectionFactory() throws Exception {
        String host = "r-2zee50zxi5iiqm****.redis.rds-aliyun.rds.aliyuncs.com:6379";
        String user = "default";
        String password = "Pas***23";
        String trustStoreFilePath = "/root/ApsaraDB-CA-Chain.jks";

        List<String> clusterNodes = Arrays.asList(host);
        RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration(clusterNodes);
        redisClusterConfiguration.setUsername(user);
        redisClusterConfiguration.setPassword(password);

        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        // Jumlah maksimum koneksi idle. Dalam mode koneksi langsung, klien terhubung langsung ke shard database. Pastikan nilai dari (Jumlah mesin bisnis × MaxTotal) lebih kecil dari jumlah maksimum koneksi untuk satu shard database.
        jedisPoolConfig.setMaxTotal(30);
        jedisPoolConfig.setMaxIdle(20);
        jedisPoolConfig.setMinIdle(20);

        final SSLSocketFactory sslSocketFactory = createTrustStoreSSLSocketFactory(trustStoreFilePath);
        JedisClientConfiguration jedisClientConfiguration = JedisClientConfiguration.builder().useSsl()
            .sslSocketFactory(sslSocketFactory).and().usePooling().poolConfig(jedisPoolConfig).build();

        return new JedisConnectionFactory(redisClusterConfiguration, jedisClientConfiguration);
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        return redisTemplate;
    }
}

Dengan Lettuce

import java.io.File;

import io.lettuce.core.ClientOptions;
import io.lettuce.core.SslOptions;
import io.lettuce.core.SslVerifyMode;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;

@Configuration
public class RedisConfig {
    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        String host = "r-2zee50zxi5iiqm****.redis.rds-aliyun.rds.aliyuncs.com";
        int port = 6379;
        String user = "default";
        String password = "Pas***23";
        String trustStoreFilePath = "/root/ApsaraDB-CA-Chain.jks";

        ClientOptions clientOptions = ClientOptions.builder().sslOptions(
            SslOptions.builder().jdkSslProvider().truststore(new File(trustStoreFilePath)).build()).build();

        RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration();
        clusterConfiguration.clusterNode(host, port);
        clusterConfiguration.setUsername(user);
        clusterConfiguration.setPassword(password);

        LettuceClientConfiguration lettuceClientConfiguration = LettuceClientConfiguration.builder()
            .clientOptions(clientOptions)
            .useSsl()
            .disablePeerVerification()
            .build();
        return new LettuceConnectionFactory(clusterConfiguration, lettuceClientConfiguration);
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        return redisTemplate;
    }
}

Lettuce

Contoh ini menggunakan Lettuce versi 6.3.0.RELEASE. Gunakan versi terbaru.

import java.io.File;
import java.time.Duration;

import io.lettuce.core.RedisURI;
import io.lettuce.core.SocketOptions;
import io.lettuce.core.SocketOptions.KeepAliveOptions;
import io.lettuce.core.SocketOptions.TcpUserTimeoutOptions;
import io.lettuce.core.SslOptions;
import io.lettuce.core.SslVerifyMode;
import io.lettuce.core.cluster.ClusterClientOptions;
import io.lettuce.core.cluster.ClusterTopologyRefreshOptions;
import io.lettuce.core.cluster.RedisClusterClient;
import io.lettuce.core.cluster.api.StatefulRedisClusterConnection;

public class SSLClusterExample {
    /**
     *  TCP_KEEPALIVE diaktifkan, dan tiga parameter dikonfigurasikan sebagai berikut:
     *  TCP_KEEPIDLE = 30
     *  TCP_KEEPINTVL = 10
     *  TCP_KEEPCNT = 3
     */
    private static final int TCP_KEEPALIVE_IDLE = 30;

    /**
     * TCP_USER_TIMEOUT dapat mencegah Lettuce terus-menerus timeout dalam kasus kerusakan.
     * referensi: https://github.com/lettuce-io/lettuce-core/issues/2082
     */
    private static final int TCP_USER_TIMEOUT = 30;

    public static void main(String[] args) throws Exception {
        String host = "r-2zee50zxi5iiqm****.redis.rds-aliyun.rds.aliyuncs.com";
        int port = 6379;
        String password = "Pas***23";
        String trustStoreFilePath = "/root/ApsaraDB-CA-Chain.jks";

        RedisURI uri = RedisURI.builder()
            .withHost(host)
            .withPort(port)
            .withPassword(password.toCharArray())
            .withSsl(true)
            .withVerifyPeer(SslVerifyMode.CA) // Karena sifat koneksi kluster langsung, SslVerifyMode.FULL tidak dapat digunakan. Anda harus melewati verifikasi nama host.
            .build();

        SslOptions sslOptions = SslOptions.builder()
            .jdkSslProvider()
            .truststore(new File(trustStoreFilePath)).build();

        ClusterTopologyRefreshOptions refreshOptions = ClusterTopologyRefreshOptions.builder()
            .enablePeriodicRefresh(Duration.ofSeconds(15))
            .dynamicRefreshSources(false)
            .enableAllAdaptiveRefreshTriggers()
            .adaptiveRefreshTriggersTimeout(Duration.ofSeconds(15)).build();

        // Konfigurasi TCP KeepAlive
        SocketOptions socketOptions = SocketOptions.builder()
            .keepAlive(KeepAliveOptions.builder()
                .enable()
                .idle(Duration.ofSeconds(TCP_KEEPALIVE_IDLE))
                .interval(Duration.ofSeconds(TCP_KEEPALIVE_IDLE / 3))
                .count(3)
                .build())
            .tcpUserTimeout(TcpUserTimeoutOptions.builder()
                .enable()
                .tcpUserTimeout(Duration.ofSeconds(TCP_USER_TIMEOUT))
                .build())
            .build();

        RedisClusterClient redisClient = RedisClusterClient.create(uri);
        redisClient.setOptions(ClusterClientOptions.builder()
            .socketOptions(socketOptions)
            .sslOptions(sslOptions)
            .validateClusterNodeMembership(false)
            .topologyRefreshOptions(refreshOptions).build());

        StatefulRedisClusterConnection<String, String> connection = redisClient.connect();
        connection.sync().set("key", "value");
        System.out.println(connection.sync().get("key"));
    }
}

Go

Contoh ini menggunakan klien go-redis v9.5.1. Gunakan v9.0 atau lebih baru.

package main

import (
	"context"
	"fmt"
	"io/ioutil"
	"crypto/tls"
	"crypto/x509"
	"github.com/redis/go-redis/v9"
)

var ctx = context.Background()

func main() {
        caCert, err := ioutil.ReadFile("/root/ApsaraDB-CA-Chain.pem")
	if err != nil {
		fmt.Println("Error loading CA certificate:", err)
		return
	}

	caCertPool := x509.NewCertPool()
	caCertPool.AppendCertsFromPEM(caCert)

	tlsConfig := &tls.Config{
		RootCAs:            caCertPool,
		InsecureSkipVerify: true, // Not actually skipping, we check the cert in VerifyPeerCertificate
		VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
			// Code copy/pasted and adapted from
			// https://github.com/golang/go/blob/81555cb4f3521b53f9de4ce15f64b77cc9df61b9/src/crypto/tls/handshake_client.go#L327-L344, but adapted to skip the hostname verification.
			// See https://github.com/golang/go/issues/21971#issuecomment-412836078.

			// If this is the first handshake on a connection, process and
			// (optionally) verify the server's certificates.
			certs := make([]*x509.Certificate, len(rawCerts))
			for i, asn1Data := range rawCerts {
				cert, err := x509.ParseCertificate(asn1Data)
				if err != nil {
					panic(err)
				}
				certs[i] = cert
			}

			opts := x509.VerifyOptions{
				Roots:         caCertPool,
				DNSName:       "", // <- skip hostname verification
				Intermediates: x509.NewCertPool(),
			}

			for i, cert := range certs {
				if i == 0 {
					continue
				}
				opts.Intermediates.AddCert(cert)
			}
			_, err := certs[0].Verify(opts)
			return err
		},
	}

	rdb := redis.NewClusterClient(&redis.ClusterOptions{
		Addrs:    []string{"r-2zee50zxi5iiqm****.redis.rds-aliyun.rds.aliyuncs.com:6379"},
		Username: "default",
		Password: "Pas***23",
		TLSConfig: tlsConfig,
	})

	err = rdb.Set(ctx, "key", "value", 0).Err()
	if err != nil {
		panic(err)
	}

	val, err := rdb.Get(ctx, "key").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("key:", val)
}

FAQ

  • Mengapa saya menerima kesalahan No subject alternative DNS name matching xxx found?

    Kesalahan ini terjadi jika Anda mengubah titik akhir instans atau nomor port setelah mengaktifkan TLS. Untuk menyelesaikan masalah ini, perbarui sertifikat TLS di konsol dan coba hubungkan lagi.