水滴石穿,万事功到自然成。
常学习,勤记录,我爱学习。
概要
正常情况下,连接redis集群只需要使用账号密码方式即可连接,但由于公司的项目上线时使用的某大厂的redis集群,该集群供多家厂商进行使用,故集群账号和密码不可能泄露给我们,所以大厂就提供了kerberos认证方式已供连接。
整体架构流程
kerberos认证流程可参考以下文章,写得非常详细,值得学习
技术细节
连接redis集群的方式有三种,可参考以下链接:
/**
* 安全认证使用方式四: API设置独立认证信息,可以在一个应用里面使用多个认证用户访问多个Redis集群
* <p>
* 1、通过krb5.conf、keytab路径、principal、服务端域名等初始化认证配置对象
* 2、创建JedisCluster对象,需要将独立的认证配置对象传入
*
* @since 2022-02-16
*/
public class SecureJedisClusterDemo4 {
/**
* 默认连接超时时间
*/
private static final Integer TIMEOUT = 3000;
/**
* 最大重试次数
*/
private static final Integer MAX_ATTEMPTS = 1;
public static void main(String[] args) throws KeyManagementException, NoSuchAlgorithmException {
// 初始化kerberos认证对象
AuthConfiguration authConfiguration = new AuthConfiguration("path1/krb5.conf", "path1/user.keytab",
"user@HADOOP1.COM");
authConfiguration.setServerRealm("HADOOP1.COM");
authConfiguration.setLocalRealm("HADOOP1.COM");
AuthConfiguration authConfiguration2 = new AuthConfiguration("path2/krb5.conf", "path2/user.keytab",
"user@HADOOP.COM");
authConfiguration.setServerRealm("HADOOP.COM");
authConfiguration.setLocalRealm("HADOOP.COM");
// 初始化第二套集群的认证信息
Set<HostAndPort> hosts = new HashSet<>();
hosts.add(new HostAndPort("ip1", 22400));
Set<HostAndPort> hosts2 = new HashSet<>();
hosts2.add(new HostAndPort("ip2", 22400));
// 初始化连接池
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMinIdle(3);
jedisPoolConfig.setMaxTotal(100);
jedisPoolConfig.setMaxIdle(100);
// 创建ssl连接新消息
boolean ssl = false;
final SSLSocketFactory socketFactory = SslSocketFactoryUtil.createTrustALLSslSocketFactory();
// 初始化jedisCluster连接
writeCluster(hosts, jedisPoolConfig, socketFactory, ssl, authConfiguration);
// 连接第二个集群的JedisCluster连接
writeCluster(hosts2, jedisPoolConfig, socketFactory, ssl, authConfiguration2);
}
/**
* 连接Redis集群
*
* @param hosts 主实例列表
* @param jedisPoolConfig 连接池配置
* @param socketFactory ssl 忽略证书的SSLSocketFactory
* @param ssl 是否开启ssl
* @param authConfiguration 安全认证配置信息
*/
private static void writeCluster(Set<HostAndPort> hsots, JedisPoolConfig jedisPoolConfig,
SSLSocketFactory socketFactory, boolean ssl, AuthConfiguration authConfiguration) {
JedisCluster client = new JedisCluster(hosts, TIMEOUT, TIMEOUT, TIMEOUT, MAX_ATTEMPTS, jedisPoolConfig, ssl,
socketFactory, authConfiguration);
client.set("test-key", System.currentTimeMillis() + "");
System.out.println(client.get("test-key"));
client.del("test-key");
client.close();
}
}
/**
* 安全认证使用方式二: API设置方式
*
* 1. 构造AuthConfiguration对象,指定keytab文件路径,principal,krb5.conf文件路径
* 若krb5.conf文件路径不指定,默认读取java.security.krb5.conf系统参数的值
*
* 2. GlobalConfig.setAuthConfiguration设置为上面创建的AuthConfiguration对象
*
* 3. 创建JedisCluster对象,创建方式同非安全一样
*
* 4. 若Redis服务是全新安装服务,需要设置SERVER_REALM环境参数,该参数值为KrbServer服务配置项default_realm的值(该配置可到管理页面查询)。
* 若Redis服务是由低版本升级而来,则不能设置该参数。
*
* @since 2020-09-30
*/
public class SecureJedisClusterDemo2 {
public static void main(String[] args) {
System.setProperty("java.security.krb5.conf", "krb5.conf file path");
AuthConfiguration authConfiguration = new AuthConfiguration("keytab file path", "principal");
GlobalConfig.setAuthConfiguration(authConfiguration);
// System.setProperty("SERVER_REALM","HADOOP.COM");
Set<HostAndPort> hosts = new HashSet<HostAndPort>();
hosts.add(new HostAndPort(Const.IP_1, Const.PORT_1));
JedisCluster client = new JedisCluster(hosts, 5000);
client.set("test-key", System.currentTimeMillis() + "");
System.out.println(client.get("test-key"));
client.del("test-key");
client.close();
}
}
/**
* 安全认证使用方式三: 使用jaas文件配置方式
*
* 1. 必须设置redis.authentication.jaas, java.security.auth.login.config, java.security.krb5.conf 3个系统参数
* 可通过java命令行-D参数设置
*
* jaas.conf文件样例:
* Client{
* com.sun.security.auth.module.Krb5LoginModule required
* useKeyTab=true
* keyTab="/xxx/user.keytab"
* principal="xxxx"
* useTicketCache=false
* storeKey=true
* debug=false;
* };
*
* 2. 若Redis服务是全新安装服务,需要设置SERVER_REALM环境参数,该参数值为KrbServer服务配置项default_realm的值(该配置可到管理页面查询)。
* 若Redis服务是由低版本升级而来,则不能设置该参数。
*
* @since 2020-09-30
*/
public class SecureJedisClusterDemo3 {
public static void main(String[] args) {
System.setProperty("redis.authentication.jaas", "true");
System.setProperty("java.security.auth.login.config", "jaas.conf file path");
System.setProperty("java.security.krb5.conf", "krb5.conf file path");
// jaas.conf文件Section名字,不设置的话默认值为Client, 区分大小写
// System.setProperty("redis.sasl.clientconfig", "redisClient");
// System.setProperty("SERVER_REALM","HADOOP.COM");
Set<HostAndPort> hosts = new HashSet<HostAndPort>();
hosts.add(new HostAndPort(Const.IP_1, Const.PORT_1));
JedisCluster client = new JedisCluster(hosts, 5000);
client.set("test-key", System.currentTimeMillis() + "");
System.out.println(client.get("test-key"));
client.del("test-key");
client.close();
}
}
我选择了方式二:API认证方式进行连接。其中:krb5.conf文件、keytab文件、principal和SERVER_REALM环境参数需要自行设置。
问题一:不知道SERVER_REALM的值如何查找,principal的值如何查找
SERVER_REALM的值可在krb5.conf文件中查找;
principal为用户名,使用 keytab文件 就是已经生成的票据可查找,使用以下命令:
klist -ket xxxxxx.keytab
问题二:不确定自己是否连接成功redis集群
由于我的项目中既使用了redisTemplate,也使用了Jedis客户端,两种方式都去连接redis集群。项目启动后,一直报如下错误:
这个提示为小白菜连接报错,也就是使用redisTemplate方式连接是需要集群密码的,采用排除法,只需注释调就可以,但是项目使用redisLock做分布式锁,底层使用redisTemplate做的集群连接,所以换一种思路,查看jedis是否连接成功。
JedisCluster jedisCluster = null;
try {
jedisCluster = new JedisCluster(nodes, timeout, poolConfig);
//简单使用一下jedisCluster即可验证
client.set("test-key", System.currentTimeMillis() + "");
System.out.println(client.get("test-key"));
client.del("test-key");
}catch (Exception e){
log.info("kerberos认证失败");
e.printStackTrace();
}
通过以上方式,证实jedis方式连接成功,将代码中使用到的redisTemplate方式的代码全部替换为JedisCluster方式。
事情往往不是那么一帆风顺,在替换的过程中,发现reidsLock做分布式锁的核心逻辑如下:
Long res = (Long)jedisCluster.eval(redisScript.getScriptAsString(), keys, args);
而eval命令由于是高危命令,被redis服务器端禁止了,只能换种方式,使用scheduleLock方式来实现分布式定时任务锁。
小结
与其浪费时间死磕,不如试试排除法、逆向思维,这些都是解决问题的关键。