【物联网】使用RabbitMQ作为MQTT服务端并自定义设备连接权限

这篇具有很好参考价值的文章主要介绍了【物联网】使用RabbitMQ作为MQTT服务端并自定义设备连接权限。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


项目背景

最近公司启动了一个新的物联网项目,使用MQTT协议与设备通信,在比较了各大MQTT服务后,决定选用开源的RabbitMQ搭建我们的服务端。我们的目标是能够支撑10万台设备同时在线,因此比较看重集群和高可用功能,RabbitMQ在这方面十分优异,同时RabbitMQ也能够兼顾项目中的消息中间件功能,缺点是仅支持3.1.1版本的协议,但对于我们这个项目来说够用。
在设计初期考虑给每一条通讯信息加密来保证安全性,但考虑到10万台设备并发量巨大,每一条消息都加解密会导致服务器计算压力过大,因此决定在设备连接、发布消息以及监听主题时来控制权限。


一、部署RabbiqMQ

在实际生产中我们部署的是集群,在本文简化为单机模式。建议通过RPM安装RabbiqMQ,具体方法不是本文重点,可以参考这篇文章。

在安装成功后需要开启MQTT插件:

rabbitmq-plugins enable rabbitmq_mqtt

在安装RabbitMQ后默认只有guest用户,我们一般会创建一个admin用户,使用MQTTX这个工具来测试我们的MQTT服务端是否工作正常,使用admin用户连接,注意将MQTT版本改为3.1.1:
【物联网】使用RabbitMQ作为MQTT服务端并自定义设备连接权限
由于本文的重点在于设备自定义鉴权,因此服务搭建过程较为简略,如果遇到问题无法连接,请参考其他文章逐步排查。

《使用RabbitMQ搭建MQTT服务》

二、设备连接鉴权

1.开启插件

想让RabbiqMQ走我们自定义的鉴权接口,需要先开启 rabbitmq_auth_backend_http 插件,同时该插件还推荐配合 rabbitmq_auth_backend_cache 通过缓存减轻授权认证服务器压力。

rabbitmq-plugins enable rabbitmq_auth_backend_http
rabbitmq-plugins enable rabbitmq_auth_backend_cache

另外使用 rabbitmq-plugins list 命令可以查看已经开启的插件和版本。

2.修改配置

然后我们可以去下载官方示例,打开示例项目的 AuthBackendHttpController 类,并将默认用户从guest改为admin:

    private final Map<String, User> users = new HashMap<String, User>() {{
        put("admin", new User("admin", "admin", asList("administrator", "management")));
        put("springy", new User("springy", "springy", asList("administrator", "management")));
    }};

然后启动服务,稍后我们会在官方示例的基础上进行鉴权逻辑的开发。

在服务启动成功后,我们需要到服务器上修改RabbitMQ的配置以告知我们的接口地址,使用RPM安装的配置文件应该在 /etc/rabbitmq/rabbitmq.config,如果没有也可以自己创建。
使用 rabbitmqctl environment 可以看到当前默认配置:

{rabbit,
     [{auth_backends,[rabbit_auth_backend_internal]},
      {auth_mechanisms,['PLAIN','AMQPLAIN']},
...
{rabbitmq_auth_backend_cache,
     [{cache_module,rabbit_auth_cache_ets},
      {cache_module_args,[]},
      {cache_refusals,false},
      {cache_ttl,15000},
      {cached_backend,rabbit_auth_backend_internal}]},
 {rabbitmq_auth_backend_http,
     [{http_method,get},
      {resource_path,"http://localhost:8000/auth/resource"},
      {topic_path,"http://localhost:8000/auth/topic"},
      {user_path,"http://localhost:8000/auth/user"},
      {vhost_path,"http://localhost:8000/auth/vhost"}]},
...

我们需要覆盖默认配置,在 rabbitmq.config 中找到 auth_backends 项,在数组中增加 rabbit_auth_backend_cache,把 rabbitmq_auth_backend_http 这一项中四个接口地址改为正确的地址(localhost改为你的IP)。
修改成功后重启服务:

service rabbitmq-server restart 

再次使用 rabbitmqctl environment 查看配置是否生效。

3.连接鉴权

此时再使用admin/admin账号通过MQTTX工具连接服务,因该能够看到接口打印的日志“认证通过”。

下面我们开始着手改造示例中的 user 方法,我们的设备都会有自己的设备名称(deviceName)以及产品ID(productId),每个产品又会有自己的密钥(productKey),其中每台设备的设备名称不允许重复,因此我们使用产品ID和设备名称结合作为连接的用户名({deviceName}@{productId}),密码则使用上述三个字段做MD5生成。

注:实际生产中密码的生成规则会更加复杂,此处仅作示例。

假定上述三个字段分别如下:

        String deviceName = "87654321";
        String productId = "123456";
        String productKey = "abcdef";

执行代码:

        String password = Md5Utils.hash(deviceName + productId + productKey);
        System.out.println(password);

输出结果为:5b0e93055c3bf7db0fbc1eb19f2a3777

综上所述我们期望设备连接的正确用户名为:

87654321@123456

连接密码为:

5b0e93055c3bf7db0fbc1eb19f2a3777

改造后的完整代码如下:

    private final static String DEFAULT_USERNAME = "admin";

    private final static String DEFAULT_PASSWORD = "admin";

    private final static String ALLOW = "allow";

    private final static String DENY = "deny";

    private final static String PRODUCT_KEY = "abcdef";

    private final Map<String, User> users = new HashMap<String, User>() {{
        put(DEFAULT_USERNAME, new User(DEFAULT_USERNAME, DEFAULT_PASSWORD, asList("administrator", "management")));
        put("springy", new User("springy", "springy", asList("administrator", "management")));
    }};

    @RequestMapping("user")
    public String user(@RequestParam("username") String username,
                       @RequestParam("password") String password) {
        LOGGER.info("Trying to authenticate user {} password {}", username, password);
        User user = users.get(username);
        //系统默认用户直接放过,用于服务端访问及测试
        if (user != null && user.getPassword().equals(password)) {
            LOGGER.info("认证通过");
            return "allow " + collectionToDelimitedString(user.getTags(), " ");
        } else {
            //设备(客户端)用户
            if (username.contains("@")) {
                String[] array = username.split("@");
                if (array.length < 2) {
                    return DENY;
                }
                String deviceName = array[0];
                String productId = array[1];
                //开始验证密码
                String myPassword = this.genPassword(deviceName, productId);
                if (myPassword.equals(password)) { //密码验证通过
                	LOGGER.info("用户{}认证通过", username);
                    //去数据库验证是否存在该产品,如果设备是先注册到平台才允许连接,那么还需要校验deviceName,具体代码省略
                    return this.checkProduct(deviceName, productId) ? ALLOW : DENY;
                } else {
                    LOGGER.warn("用户{}认证失败", username);
                    return DENY;
                }
            } else {
                return DENY;
            }
        }
    }
    
    private String genPassword(String deviceName, String productId) {
        return Md5Utils.hash(deviceName + productId + PRODUCT_KEY);
    }

重启服务,使用MQTTX进行连接测试,如果用户名或密码错误,将会弹出提示:

Error: Connection refused: Bad username or password

如果连接正常,则会打印日志:

用户87654321认证通过

仔细观察日志会发现vhost和resource方法也被调用了,由于我们只有一个默认vhost:“/”,因此没有必要验证,全部返回allow即可,根据我们的业务resource也不需要处理,返回allow。

4.消息鉴权

在改造代码前我们先简单说一说MQTT中的主题设计,当若干设备把消息发送到RabbitMQ后,RabbitMQ会根据订阅情况把消息转发给订阅者,假设我们所有设备都使用同一个主题发送消息,那么我们订阅者(服务端)只能通过解析报文后获取设备名称来区分消息的发送者,因此,任何一台设备都可以通过修改报文来模拟成其他任意一台设备,这属于一个潜在风险。

为了避免上述风险,我们希望能够限制设备连接到RabbitMQ后的行为,避免设备发送和获取自己权限外的数据,具体的方法就是每台设备使用自己的主题与订阅者(服务端)交互,MQTT协议中主题并不是预设的,是可以在运行时任意创建的,因此可以使用设备名称和产品ID动态创建主题。

我们假设设备上送消息的主题为:

topic/{deviceName}/{productId}/upload

服务端响应的主题为:

topic/{deviceName}/{productId}/upload/reply

下面我们着手改造topic方法,topic方法有一个参数TopicCheck类,其中对我们有用的属性是routing_key、username、permission。
设备发送消息到 topic/87654321/123456/upload 这个主题时,routing_key参数的值为 topic.87654321.123456.upload,把".“替换成”/"则可以还原主题。username是连接时的用户名,我们可以从中获取设备名称和产品ID,permission分为读和写,对应的是消息的监听和发送,这两个主题是不同的。

改造后代码如下:

//设备允许订阅的主题
    private static final List<String> deviceReadAllowTopic = Arrays.asList("topic/%s/%s/upload/reply")
    //设备允许发布的主题
    private static final List<String> deviceWriteAllowTopic = Arrays.asList("topic/%s/%s/upload")

    @RequestMapping("topic")
    public String topic(TopicCheck check) {
        LOGGER.info("校验topic={} ", check.getRouting_key());
        String username = check.getUsername();
        if (DEFAULT_USERNAME.equals(username)) { //默认管理员账户直接放过
            return ALLOW;
        }
        String permission = check.getPermission();
        String[] array = username.split("@");
        String deviceName = array[0];
        String productId = array[1];
        String routingKey = check.getRouting_key();
        String topic = routingKey.replaceAll("\\.", "/");
        if ("read".equals(permission)) {
            for (String s : deviceReadAllowTopic) {
                String f = String.format(s, deviceName, productId);
                if (f.equals(topic)) { //匹配上了则放过
                    return ALLOW;
                }
            }
            LOGGER.warn("设备{}订阅主题{}不在允许的列表内", username, topic);
            return DENY;
        } else if ("write".equals(permission)) {
            for (String s : deviceWriteAllowTopic) {
                String f = String.format(s, deviceName, productId);
                if (f.equals(topic)) { //匹配上了则放过
                    return ALLOW;
                }
            }
            LOGGER.warn("设备{}发布主题{}不在允许的列表内", username, topic);
            return DENY;
        } else {
            return ALLOW;
        }
    }

使用MQTTX测试,会看到如果尝试给权限范围外的主题发送消息或者订阅没有权限的主题则会断开连接。

总结

至此,对RabbitMQ的权限控制就完成了,同学们可以根据项目的实际情况来修改鉴权方式,如果设备数量较多,则务必使用缓存缓解并发压力。

源码下载文章来源地址https://www.toymoban.com/news/detail-511713.html

到了这里,关于【物联网】使用RabbitMQ作为MQTT服务端并自定义设备连接权限的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【自用】云服务器 使用 docker 搭建 HomeAssistant + MQTT 物联网平台

    1.搭建流程概述 2.准备工作 3.开始搭建! 4.总结 如果想看 ESP32 或其他使用 MicroPython 编程的单片机如何连接到该云服务器,实现 HomeAssistant 控制 单片机的内容,请看我这篇博客的下一篇。 0.总体流程 我们需要先有一台云服务器,然后在上面搭建 docker(用宝塔傻瓜式搭建就行

    2024年02月13日
    浏览(50)
  • 基于ESP32搭建物联网服务器十二(使用MQTT协议与ESP32互动)

    在之前的文章中:基于ESP32搭建物联网服务器十一(用WEB页面控制引脚(GPIO)功能)_esp32webserver 控制io_你的幻境的博客-CSDN博客 已经简单地介绍了MQTT协议,对比于其它网络协议,MQTT协议在物联网的开发中,它的特点使它适用于大多数受限的环境。例如网络代价昂贵,带宽低、不可

    2024年02月02日
    浏览(50)
  • 使用Python创建websocket服务端并给出不同客户端的请求

    作者:虚坏叔叔 博客:https://xuhss.com 早餐店不会开到晚上,想吃的人早就来了!😄 WebSocket 和HTTP一样,也是一种通讯协议,允许服务端主动向客户端推送数据。 在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据

    2024年01月17日
    浏览(31)
  • 使用合宙Air724UG物联网模块发送MQTT消息至EMQX服务器 MQTT如何发送消息 AIR724发送MQTT至腾讯云 腾讯云接收MQTT消息

    在上一篇关于物联网的文章中介绍了如何建立一个MQTT的EMQX服务器,有需要的同学可以点击查看。在这里服务器的作用相当于建立一个MQTT消息的中转站,消息先发送到服务器中,再在服务器进行转发消息。 那么有了一个转发的平台,如何在嵌入式终端中发送MQTT消息呢,在这

    2024年02月09日
    浏览(58)
  • 【物联网】物1— 初步认识MQTT、连接到MQTT服务端

    目录 一、MQTT是什么 二、MQTT的版本 两者之间的关系: ​三、MQTT工作的基本原理 3.1、概念 MQTT客户端: MQTT服务端: MQTT主题: 3.2、MQTT订阅/发布主题的特点 相互可独立性: 空间可分离: 时间可异步: 四、连接到MQTT服务器端 4.1理论篇 两个步骤 4.2实战篇 电脑端MQTT客户端连

    2024年02月13日
    浏览(39)
  • MQTT服务器详细介绍:连接物联网的通信枢纽

    随着物联网技术的不断发展,MQTT(Message Queuing Telemetry Transport)协议作为一种轻量级、可靠、灵活的通信协议,被广泛应用于物联网领域。在MQTT系统中,MQTT服务器扮演着重要的角色,作为连接物联网设备和应用程序的通信枢纽。本文将详细介绍MQTT服务器的组成、运行机理、

    2024年02月10日
    浏览(54)
  • 阿里云Ubuntu安装部署EMQX物联网MQTT服务器

    阿里云服务器免费领取https://developer.aliyun.com/adc/student/ Xshell 云服务器可以通过远程连接的方式进行控制 1.下载安装包 XShell官网  2.简单配置 名称:随便即可 主机:服务器IP地址 端口号:默认22端口 连接后输入用户名(通常为root),密码后成功进入服务器终端    此外,还有

    2023年04月13日
    浏览(43)
  • 【物联网开发】-微信小程序之MQTT连接,基于MQTT实现设备-服务器-小程序的消息传输

    想要开发微信小程序,首先要有一些基础知识:html、cs、js、json等,小程序中要用到的知识框架大体相同,一个页面包括js、json、wxml、wxss格式的文件。 由于本人此前从未接触过小程序开发,本篇文章将会以新手小白的角度一步步剖析如何使用微信小程序通过MQTT服务器连接设

    2023年04月24日
    浏览(59)
  • 【CSS】首个字符占用多行,并自定义样式

    效果 代码

    2024年02月01日
    浏览(37)
  • 物联网通信协议-MQTT及使用python实现

    简述 MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的\\\"轻量 级\\\"通讯协议,该协议构建于TCP/IP协议上,由IBM在1999年发布。MQTT最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。作

    2024年02月10日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包