【Redis深度专题】「踩坑技术提升」一文教会你如何在支持Redis在低版本Jedis情况下兼容Redis的ACL机制

Redis低版本客户端兼容高版本Jedis不支持ACL的问题

首先,针对于Redis6.0之后,已经可以支持通过ACL的访问控制列表的机制进行控制多个用户进行权限控制访问,并且更加精细的控制权限访问处理模式,更加的偏向于RBAC模型的机制体系。

Redis的ACL是什么?

Redis的ACL(Access Control List)是一种用于访问控制的功能,它允许您对Redis服务器进行细粒度的权限管理和访问控制。通过使用ACL,您可以限制对Redis命令和数据的访问,以保护您的Redis实例免受未经授权的访问和滥用。

Redis的ACL功能提供了以下几个关键方面的控制:

  1. 用户和角色管理:ACL允许您创建和管理多个用户,并为每个用户分配适当的角色。角色定义了用户可以执行的命令和访问的数据范围。

  2. 命令级别的权限控制:您可以为每个角色指定允许执行的命令和禁止执行的命令。这使您能够限制用户对特定命令的访问权限。

  3. 数据级别的权限控制:ACL还允许您为每个角色指定可以访问的数据库和键空间。这使您能够限制用户对特定数据的访问权限。

  4. 密码和认证:ACL允许您为每个用户设置密码,并要求用户在连接到Redis服务器时进行身份验证。这提供了一种基本的身份验证机制,以确保只有经过身份验证的用户才能访问Redis。

通过使用ACL,您可以实现对Redis的细粒度访问控制,确保只有经过授权的用户才能执行特定的命令和访问特定的数据。这有助于提高Redis实例的安全性,并防止未经授权的访问和滥用。

POC调研计划方案

目前公司存在了某种需求问题,需要进行调整和升级Redis版本,并且倾向于通过用户(可以理解为角色)+ 密码的方式进行对于Redis访问的控制。

poc截止到了现在应该是勉强可以了,不过还有一些问题,但是应该是可以实现了,大概使用了4套方案:

  1. 升级整体框架版本整体框架,从而升级客户端版本,后面发现和代码的适配问题会引发很多不必要的坑,以及后续改动量实在太大了,我大概POC花费了2d时间去处理。

  2. 不修改整体框架版本,只修改单纯的客户端版本,我修改了很多源码,以及对应的客户端底库,最后发现对于集群模式的时候会出现很多问题,而且DBS目前才有的应该是集群模式吧,所以暂时放弃了,大概POC花费了1d时间

  3. 升级客户端底层仓库,不修改客户端以及框架,后面我也是在减少项目代码的变化,然后将jedis的底层源码进行修改了,但是发现了发现可以实现,但是改动量实在是太大了,基本上修改了整体的源码仓库才可能性,而且出现了对于so库的仓库的加载的问题,POC花费了2d,工作l量至少需要1w人天。

4. (推荐)全流成改造我们的项目代码,方便所有的redisson和jedis代码进行改造和优化调整,以及jedis的对应兼容acl的用户名认证的代码,以及添加对应的配置实现,并且加入适配的代码即可,从而可以实现对应的对应的兼容ACL模式下的:用户名和密码登录。POC花费了2d,工作量还是比较大,我预测大概至少3-4d的实现改造所有服务的代码(1d)的时间进行自测。

Jedis升级版本功能实现控制

Jedis源码改造原则

在不改造任何版本框架版本的场景下,仅仅进行使用适配的模型和能力进行实现用户名和密码的登陆方式。其中就要用到一个原则就是项目目录与jar包中的文件相同的全限定名的时候,会优先采用项目目录中的类进行加载,从而我们就获得了进行覆盖jedis源码的能力。

覆盖Auth的Jedis操作认证

我们将整个BinaryClient全部拷贝出来,放到我们的项目里面,并且包名必须为:redis.clients.jedis。

面向的类为:redis.clients.jedis.BinaryClient。在较低版本的源码为:

java 复制代码
 public void auth(final String password) {
         setPassword(password);
         sendCommand(AUTH, password);
 }

对此,我们需要进行对于高版本的代码变更一下Redis的指令auth从参数角度进行变更即可。

指令修改和代码调整兼容

复制代码
auth password

变更为支持用户名+密码的认证模式机制

复制代码
auth userName password

故此我们将代码修改为:

java 复制代码
 public void auth(final String password) {
        String userName = RedisUserName.redisUserName;
        if(StringUtils.isNotBlank(userName)){
            setPassword(password);
            sendCommand(AUTH,userName, password);
        }
        else {
            setPassword(password);
            sendCommand(AUTH, password);
        }
  }

此外还考虑了兼容单独密码的模式,所以进行调整了兼容两种模式都共存的if语句,嘻嘻。

这样子将底层的功能进行直接获取用户名进行控制,减少了很多的开发量问题。并且对于Spring框架、甚至我们自己的框架都是透明化的问题。

Redis用户名的传输和配置

接下来就是最后一个问题,配置用户名以及如何传递给他,我在这里使用了一个简单的方案就是通过静态变量进行传输。方便我们全局访问!

从下面的配置可以看到,我们属于使用RedisUserName类进行访问此类的redisUserName变量进行访问。那么这个值是怎么来的?

直接上源码干货

不说太多其他的,直接上干货即可。

java 复制代码
@Component
public class RedisUserName implements BeanPostProcessor {

    public static String redisUserName;

    @Value("${spring.redis.userName:root}")
    private String userName;

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if(bean instanceof RedisConfig && StringUtils.isEmpty(redisUserName)){
            RedisUserName.class.getClassLoader();
            try {
                Class.forName(RedisUserName.class.getName());
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
            RedisUserName.redisUserName = userName;
        }
        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if(bean instanceof RedisConfig && StringUtils.isEmpty(redisUserName)){
            RedisUserName.class.getClassLoader();
            try {
                Class.forName(RedisUserName.class.getName());
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
            RedisUserName.redisUserName = userName;
        }
        return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
    }
}

实现了Spring的BeanPostProcessor接口的类的方法。它的作用是在Bean初始化之前对特定的Bean进行处理。

  1. public static String redisUserName;:定义了一个静态的字符串变量redisUserName,用于存储Redis的用户名。

  2. @Value("${spring.redis.userName:root}"):使用Spring的@Value注解,将配置文件中的spring.redis.userName属性的值注入到userName变量中。如果配置文件中没有配置该属性,则默认值为"root"。

  3. postProcessBeforeInitialization方法:这是BeanPostProcessor接口的一个方法,用于在Bean初始化之前进行处理。在这个方法中,首先判断当前处理的Bean是否是RedisConfig类型,并且redisUserName为空。如果满足条件,则执行以下操作:

    • RedisUserName.class.getClassLoader();:这行代码没有实际作用,可能是作者误写或者遗留的无用代码。

    • Class.forName(RedisUserName.class.getName());:通过反射加载RedisUserName类,这行代码也没有实际作用,可能是作者误写或者遗留的无用代码。

    • RedisUserName.redisUserName = userName;:将userName的值赋给redisUserName静态变量,即将配置文件中的spring.redis.userName属性的值赋给redisUserName变量。

  4. 最后,返回BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);,继续执行其他的BeanPostProcessor的处理逻辑。

总结介绍说明

在RedisConfig类的Bean初始化之前,将配置文件中的spring.redis.userName属性的值赋给静态变量redisUserName。这样可以在其他地方通过访问RedisUserName.redisUserName来获取Redis的用户名。但是代码中的部分内容,如RedisUserName.class.getClassLoader();Class.forName(RedisUserName.class.getName());似乎没有实际作用,可能是作者误写或者遗留的无用代码。

修改配置和调整控制

设置对应的ACL的用户和设置指令

主要用于对应SAAS环境的中用户和我们Redis的ACL是一个概念的用户设计。

增加配置文件中的key或者环境变量

yml 复制代码
# 主要用于Jedis客户端的连接使用
spring.redis.userName: {用户名}
# 主要用于Redisson客户端的连接使用
redisson.userName: {用户名}

修改对应的配置信息

yml 复制代码
spring.redis.password:{你在设置ACL的时候所制定的密码}

设置Redis的ACL用户指令

设置用户密码

csharp 复制代码
ACL SETUSER {用户名} on >{你的密码}

设置用户整体操作权限

css 复制代码
> acl setuser {用户名} ~* &* +@all
相关推荐
kkoral4 分钟前
单机docker部署的redis sentinel,使用python调用redis,报错
redis·python·docker·sentinel
大学生资源网17 分钟前
基于springboot的万亩助农网站的设计与实现源代码(源码+文档)
java·spring boot·后端·mysql·毕业设计·源码
苏三的开发日记26 分钟前
linux端进行kafka集群服务的搭建
后端
苏三的开发日记1 小时前
windows系统搭建kafka环境
后端
爬山算法1 小时前
Netty(19)Netty的性能优化手段有哪些?
java·后端
Tony Bai1 小时前
Cloudflare 2025 年度报告发布——Go 语言再次“屠榜”API 领域,AI 流量激增!
开发语言·人工智能·后端·golang
想用offer打牌1 小时前
虚拟内存与寻址方式解析(面试版)
java·后端·面试·系统架构
無量1 小时前
AQS抽象队列同步器原理与应用
后端
java1234_小锋2 小时前
Redis6为什么引入了多线程?
java·redis
9号达人2 小时前
支付成功订单却没了?MyBatis连接池的坑我踩了
java·后端·面试