一、生成SSL证书
创建证书存放目录
mkdir -p /opt/redis/ssl
分别生成CA证书及客户端证书,key等文件
# 进入上诉新建的证书存放目录
cd /opt/redis/ssl
# 依次执行以下命令生成证书
openssl genrsa -out ca.key 4096
openssl req -x509 -new -nodes -sha256 -key ca.key -days 3650 -subj '/O=Redis Test/CN=Certificate Authority' -out ca.crt
openssl genrsa -out redis.key 2048
openssl req -new -sha256 -key redis.key -subj '/O=Redis Test/CN=Server' | openssl x509 -req -sha256 -CA ca.crt -CAkey ca.key -CAserial ca.txt -CAcreateserial -days 365 -out redis.crt
openssl dhparam -out redis.dh 2048
二、Redis SSL配置
2.1、不验证客户端证书的配置方式
tls-port 6379
tls-cert-file /opt/redis/ssl/redis.crt
tls-key-file /opt/redis/ssl/redis.key
#tls-ca-cert-file /Users/rootCA.pem
#tls-ca-cert-dir /etc/ssl/certs
tls-auth-clients no
客户端可以不传自己的证书,只传CA证书(用于验证服务端)。
$ redis-cli --tls --cacert /opt/redis/ssl/ca.crt
127.0.0.1:6379> set a 1
OK
127.0.0.1:6379>
2.2、客户端证书可选的配置方式
tls-port 6379
tls-cert-file /opt/redis/ssl/redis.crt
tls-key-file /opt/redis/ssl/redis.key
tls-ca-cert-file /opt/redis/ssl/ca.crt
tls-auth-clients optional
客户端证书可以不传,可以建立连接。
$ redis-cli --tls --cacert /opt/redis/ssl/ca.crt
127.0.0.1:6379> set a 1
OK
客户端证书也可以传,可以建立连接。
$ redis-cli --tls --cacert /opt/redis/ssl/ca.crt --cert /opt/redis/ssl/redis.crt --key /opt/redis/ssl/redis.key
127.0.0.1:6379> set a 1
OK
客户端证书也可以传,但是传的是错误的,不可以建立连接。
$ redis-cli --tls --cacert /Users/rootCA.pem --cert /Users/server.pem --key /Users/none.key
Could not negotiate a TLS connection: Invalid private key
not connected>
2.3、验证客户端的方式
################################## TLS 配置 ###################################
tls-cert-file /opt/redis/ssl/redis.crt
tls-key-file /opt/redis/ssl/redis.key
tls-ca-cert-file /opt/redis/ssl/ca.crt
tls-dh-params-file /opt/redis/ssl/redis.dh
tls-auth-clients yes
tls-replication yes
#指定tls-replication yes才能将TLS用于到主服务器的对外连接,sentinel也需要同步设置。
#tls-cluster yes
- 服务启动
/usr/local/bin/redis-server /usr/local/redis-6.0.12/redis.conf
- redis-cli客户端命令行访问
redis-cli --tls --cert /opt/redis/ssl/redis.crt --key /opt/redis/ssl/redis.key --cacert /opt/redis/ssl/ca.crt -p 6380
以上即完成Redis6.x的TLS安全通道配置部署。
三、Jedis连接SSL
将上述中生成的ca.cert、redis.cert、redis.key文件提取出来放在我们java项目中的resource目录下(当然我只是测试所以没有规范存放): 编写SSLSocketFactory生成类
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.60</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.60</version>
</dependency>
生成SSLSocketFactory代码示例:
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.core.io.ClassPathResource;
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;
/**
* SocketFactory
* @date 2021/12/20 16:43
* @Description socket工厂
*/
public class SocketFactory {
/**
* 创建 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();
}
}
}
public static void main(String[] args) {
try {
SSLSocketFactory socketFactory = getSocketFactory("ca.crt", "redis.crt", "redis.key", "D8769D08908529D6");
System.out.println(666);
} catch (Exception e) {
e.printStackTrace();
}
}
}
- jedis使用SSL调用redis服务示例
SSLSocketFactory socketFactory = SocketFactory.getSocketFactory("ca.crt", "redis.crt", "redis.key", "D8769D08908529D6");
JedisPool jedisPool = new JedisPool(new JedisPoolConfig(),
"127.0.0.1",
6380,
0,
"123456",
true,
socketFactory,null,null);
Jedis resource = jedisPool.getResource();
resource.set("test","123");
四、Spring与Redis
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
private static final Logger LOGGER = LoggerFactory.getLogger(RedisConfig.class);
@Value("${spring.redis.host}")
String host;
@Value("${spring.redis.port}")
int port;
@Value("${spring.redis.password}")
String passWord;
@Value("${spring.redis.database}")
int database;
@Value("${spring.redis.jedis.pool.max-idle}")
int maxIdle;
@Value("${spring.redis.jedis.pool.max-total}")
int maxTotal;
@Value("${spring.redis.jedis.pool.max-wait}")
long maxWait;
@Value("${spring.redis.jedis.pool.min-idle}")
int minIdle;
@Value("${spring.redis.trust-store}")
String trustStoreFileName;
@Value("${spring.redis.trust-store-password}")
String trustStorePassword;
@Value("${spring.redis.ssl-enabled}")
boolean isSSL;
@Autowired
private AesCiperServiceUtil aesCipherService;
/**
* retemplate相关配置
*
* @param factory RedisConnectionFactory对象
* @return RedisTemplate<String, Object>
*/
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
StringRedisTemplate template = new StringRedisTemplate(factory);
template.setValueSerializer(new StringRedisSerializer());
template.setKeySerializer(new StringRedisSerializer());
template.afterPropertiesSet();
return template;
}
/**
* redisConnectionFactory redisConnection工厂
*
* @return JedisConnectionFactory JedisConnection工厂
*/
@Bean
public JedisConnectionFactory redisConnectionFactory() {
String pwd = aesCipherService.decryptStr(passWord);
try {
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
redisStandaloneConfiguration.setHostName(host);
redisStandaloneConfiguration.setPort(port);
redisStandaloneConfiguration.setPassword(pwd);
redisStandaloneConfiguration.setDatabase(database);
JedisClientConfiguration.JedisPoolingClientConfigurationBuilder jedisPoolingClientConfigurationBuilder =
JedisClientConfiguration.builder().usePooling().poolConfig(jedisPoolConfig());
JedisClientConfiguration jedisClientConfiguration = jedisPoolingClientConfigurationBuilder.build();
if (isSSL) {
try {
jedisClientConfiguration = jedisPoolingClientConfigurationBuilder.and().useSsl()
.sslSocketFactory(createTrustStoreSSLSocketFactory()).build();
} catch (CertificateOperationException | UnrecoverableKeyException e) {
CodeCCUtils.error(LOGGER, "redis use ssl error");
}
}
return new JedisConnectionFactory(redisStandaloneConfiguration, jedisClientConfiguration);
} finally {
pwd = null;
}
}
/**
* jedisPoolConfig
*
* @return JedisPoolConfig jedisPoolConfig
*/
@Bean
public JedisPoolConfig jedisPoolConfig() {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(maxTotal);
jedisPoolConfig.setMaxIdle(maxIdle);
jedisPoolConfig.setMinIdle(minIdle);
jedisPoolConfig.setMaxWaitMillis(maxWait);
return jedisPoolConfig;
}
private SSLSocketFactory createTrustStoreSSLSocketFactory()
throws UnrecoverableKeyException, CertificateOperationException {
String pwd = aesCipherService.decryptStr(trustStorePassword);
SSLContext sslContext;
try {
sslContext = SSLUtil.getSSLContext(trustStoreFileName, pwd);
} finally {
pwd = null;
}
return sslContext.getSocketFactory();
}
}
更多内容关注微信公众号 ”前后端技术精选“,或者语雀,里面有更多知识:www.yuque.com/riverzmm/uu… 《安全》