Springboot关闭redis SSL证书校验

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

你可能感兴趣的:(土味,redis,spring,boot,ssl)