redis开启了TLS/SSL后, Springboot项目需要进行修改, 可以配置证书进行校验, 也可以直接关闭校验,。直接关闭校验比较简单,配置文件也不需要修改
package com.shensu.jmb.config;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMDecryptorProvider;
import org.bouncycastle.openssl.PEMEncryptedKeyPair;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.util.Pool;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.Security;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
/**
* SSL的JedisConnectionFactory
* @author ZXZ
*
*/
@Configuration
public class SSLJedisConnectionFactory extends JedisConnectionFactory{
/**
* 重写createRedisPool方法,让其使用SslSocketFactory创建连接池
*/
protected Pool<Jedis> createRedisPool() {
SSLSocketFactory socketFactory = null;
try {
// redis服务器上开启TLS后的证书和密码(txt后缀)
socketFactory = getSocketFactory("ca.crt", "redis.crt", "redis.key", "AC340A99205FA978");
} catch (Exception e) {
throw new RuntimeException(e);
}
return new JedisPool(super.getPoolConfig(),
"192.168.10.168",
7000,
1000,
"redis7_app",
true,
socketFactory,null,null);
}
/**
* 创建 SSLSocketFactory 工厂
*
* @param caCrtFile 服务端 CA 证书
* @param crtFile 客户端 CRT 文件
* @param keyFile 客户端 Key 文件
* @param password SSL 密码,随机
* @return {@link SSLSocketFactory}
* @throws Exception 异常
*/
public static SSLSocketFactory getSocketFactory(final String caCrtFile, final String crtFile, final String keyFile, final String password) throws Exception {
InputStream caInputStream = null;
InputStream crtInputStream = null;
InputStream keyInputStream = null;
try {
Security.addProvider(new BouncyCastleProvider());
CertificateFactory cf = CertificateFactory.getInstance("X.509");
// load CA certificate
caInputStream = new ClassPathResource(caCrtFile).getInputStream();
X509Certificate caCert = null;
while (caInputStream.available() > 0) {
caCert = (X509Certificate) cf.generateCertificate(caInputStream);
}
// load client certificate
crtInputStream = new ClassPathResource(crtFile).getInputStream();
X509Certificate cert = null;
while (crtInputStream.available() > 0) {
cert = (X509Certificate) cf.generateCertificate(crtInputStream);
}
// load client private key
keyInputStream = new ClassPathResource(keyFile).getInputStream();
PEMParser pemParser = new PEMParser(new InputStreamReader(keyInputStream));
Object object = pemParser.readObject();
PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(password.toCharArray());
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
KeyPair key;
if (object instanceof PEMEncryptedKeyPair) {
System.out.println("Encrypted key - we will use provided password");
key = converter.getKeyPair(((PEMEncryptedKeyPair) object).decryptKeyPair(decProv));
} else {
System.out.println("Unencrypted key - no password needed");
key = converter.getKeyPair((PEMKeyPair) object);
}
pemParser.close();
// CA certificate is used to authenticate server
KeyStore caKs = KeyStore.getInstance(KeyStore.getDefaultType());
caKs.load(null, null);
caKs.setCertificateEntry("ca-certificate", caCert);
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init(caKs);
// client key and certificates are sent to server so it can authenticate
// us
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, null);
ks.setCertificateEntry("certificate", cert);
ks.setKeyEntry("private-key", key.getPrivate(), password.toCharArray(), new java.security.cert.Certificate[]{cert});
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, password.toCharArray());
// finally, create SSL socket factory
SSLContext context = SSLContext.getInstance("TLSv1.2");
context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
return context.getSocketFactory();
}
finally {
if (null != caInputStream) {
caInputStream.close();
}
if (null != crtInputStream) {
crtInputStream.close();
}
if (null != keyInputStream) {
keyInputStream.close();
}
}
}
}
@Configuration(proxyBeanMethods = false)
public class RedisSSLConfiguration implements LettuceClientConfigurationBuilderCustomizer {
@Override
public void customize(LettuceClientConfigurationBuilder clientConfigurationBuilder) {
// 关闭ssl证书校验
clientConfigurationBuilder.useSsl().disablePeerVerification();
}
}
// 找到redis配置入口
org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration#redisConnectionFactory
@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
LettuceConnectionFactory redisConnectionFactory(
ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
ClientResources clientResources) {
// 获取配置信息, 连接属性ip,端口,数据库,密码都在 protected final RedisProperties getProperties() 方法里面
LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(builderCustomizers, clientResources,
getProperties().getLettuce().getPool());
// 创建连接工厂
return createLettuceConnectionFactory(clientConfig);
}
private LettuceClientConfiguration getLettuceClientConfiguration(
ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
ClientResources clientResources, Pool pool) {
LettuceClientConfigurationBuilder builder = createBuilder(pool);
// 设置clientName, shutdownTimeout
applyProperties(builder);
// url是空的
if (StringUtils.hasText(getProperties().getUrl())) {
customizeConfigurationFromUrl(builder);
}
builder.clientOptions(createClientOptions());
builder.clientResources(clientResources);
// builderCustomizers所属的接口ObjectProvider继承了Iterable接口,支持stream流,可以视它为一个集合
// 如果我们实现了接口LettuceClientConfigurationBuilderCustomizer并注册为bean, 那么springboot启动时会扫描到它并添加到ObjectProvider中,这样下面的forEach就有数据, 会执行方法体里面的customize()方法;
// 如果没有实现接口LettuceClientConfigurationBuilderCustomizer, forEach()就是空循环。
// 在LettuceClientConfigurationBuilderCustomizer实现类的customize()里面, 我们可以设置不对ssl证书进行校验;
// 如果接口LettuceClientConfigurationBuilderCustomizer有多个实现类, 会依次执行它们的customize()实现方法
// ObjectProvider作用:如果注入实例为空时,使用ObjectProvider则避免了强依赖导致的依赖对象不存在异常;
builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return builder.build();
}
https://blog.csdn.net/Exception_sir/article/details/122047071
https://lettuce.io/core/release/reference/index.html#ssl.hostpeer-verification