百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

mqtt实战-EMQX MQTT 服务器启用 SSL/TLS 安全连接

bigegpt 2024-08-01 11:54 13 浏览

大家好,我是yangyang.本计划不会最近不会更新mq相关文章,奈何今天使用tls连接生成证书的时候遇到了一个坑,于是,给大家分享一下.

SSL/TLS 证书准备

通常来说,我们会需要数字证书来保证 TLS 通讯的强认证。数字证书的使用本身是一个三方协议,除了通讯双方,还有一个颁发证书的受信第三方,有时候这个受信第三方就是一个 CA。和 CA 的通讯,一般是以预先发行证书的方式进行的。也就是在开始 TLS 通讯的时候,我们需要至少有 2 个证书,一个 CA 的,一个 EMQX 的,EMQX 的证书由 CA 颁发,并用 CA 的证书验证。

获得一个真正受外界信任的证书需要到证书服务提供商进行购买。在实验室环境,我们也可以用自己生成的证书来模拟这个过程。下面我们分别以这两种方式来说明 EMQX 服务器的 SSL/TLS 启用过程。

注意: 购买证书与自签名证书的配置,读者根据自身情况只需选择其中一种进行测试。


购买证书

如果有购买证书的话,就不需要自签名证书。

为方便 EMQX 配置,请将购买的证书文件重命名为 emqx.crt,证书密钥重命名为 emqx.key

自签名证书

在这里,我们假设您的系统已经安装了 OpenSSL。使用 OpenSSL 附带的工具集就可以生成我们需要的证书了。

首先,我们需要一个自签名的 CA 证书。生成这个证书需要有一个私钥为它签名,可以执行以下命令来生成私钥:

openssl genrsa -out ca.key 2048

这个命令将生成一个密钥长度为 2048 的密钥并保存在 ca.key 中。有了这个密钥,就可以用它来生成 EMQX 的根证书了:

openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.pem

查看 CA 证书信息(可选):

openssl x509 -in ca.pem -noout -text

根证书是整个信任链的起点,如果一个证书的每一级签发者向上一直到根证书都是可信的,那个我们就可以认为这个证书也是可信的。有了这个根证书,我们就可以用它来给其他实体签发实体证书了。

实体(在这里指的是 EMQX)也需要一个自己的私钥对来保证它对自己证书的控制权。生成这个密钥的过程和上面类似:

openssl genrsa -out emqx.key 2048

新建 openssl.cnf 文件,

  • req_distinguished_name :根据情况进行修改,
  • alt_names: BROKER_ADDRESS 修改为 EMQX 服务器实际的 IP 或 DNS 地址,例如:IP.1 = 127.0.0.1,或 DNS.1 = broker.xxx.com (如果在内网测试发现连不上,可以本地host `127.0.0.1 borker.test.com` 然后这里就写:IP.1=127.0.0.1 DNS.1=borker.test.com )
[req]
default_bits  = 2048
distinguished_name = req_distinguished_name
req_extensions = req_ext
x509_extensions = v3_req
prompt = no
[req_distinguished_name]
countryName = CN
stateOrProvinceName = Zhejiang
localityName = Hangzhou
organizationName = EMQX
commonName = Server certificate
[req_ext]
subjectAltName = @alt_names
[v3_req]
subjectAltName = @alt_names
[alt_names]
IP.1 = BROKER_ADDRESS
DNS.1 = BROKER_ADDRESS

然后以这个密钥和配置签发一个证书请求:

openssl req -new -key ./emqx.key -config openssl.cnf -out emqx.csr

然后以根证书来签发 EMQX 的实体证书:

openssl x509 -req -in ./emqx.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out emqx.pem -days 3650 -sha256 -extensions v3_req -extfile openssl.cnf

查看 EMQX 实体证书(可选):

openssl x509 -in emqx.pem -noout -text

验证 EMQX 实体证书,确定证书是否正确:

$ openssl verify -CAfile ca.pem emqx.pem
emqx.pem: OK

准备好证书后,我们就可以启用 EMQX 的 TLS/SSL 功能了。

SSL/TLS 启用及验证

在 EMQX 中 mqtt:ssl 的默认监听端口为 8883。




MQTT 连接测试


意:自签证书必须要传递ca根证书,同时如果emqx开启客户端证书验证时,如果上传了客户端证书和key就必须要传入对的或者不传.购买证书直接选第一个证书类型.

php workerman/mqtt 接入代码

   public function onWorkerStart(Worker $worker)
    {
        $mqttConfig = config('mqtt.fbox');
        if (empty($mqttConfig['listen'])) {
            return;
        }

        $options = [];
        !empty($mqttConfig['username']) && $options['username'] = $mqttConfig['username'];
        !empty($mqttConfig['password']) && $options['password'] = $mqttConfig['password'];
        if (0 === strpos($mqttConfig['listen'], 'mqtts') || 0 === strpos($mqttConfig['listen'], 'wss') ) {
            if (empty($mqttConfig['ssl_ca'])) {
                echo '[FBOX]', '请申请并配置证书', PHP_EOL;
                return;
            }

            $verifyPeer = false;
            $allowSelfSigned = false;
            if ($mqttConfig['ssl_ca_type'] === 'selfSign') {
                $verifyPeer = true;
                $allowSelfSigned = true;
            }


            $options['ssl'] = [
                'verify_peer' => $verifyPeer, // 如果是自签名证书需要设置为true,因为需要验证根证书,自行购买不用
                'allow_self_signed' => $allowSelfSigned,
                'cafile' => $mqttConfig['ssl_ca']
            ];
            if (!empty($mqttConfig['ssl_key'])) {
                $options['ssl']['local_pk'] = $mqttConfig['ssl_key'];
            }

            if (!empty($mqttConfig['ssl_cert'])) {
                $options['ssl']['local_cert'] = $mqttConfig['ssl_cert'];
            }
        }

        $this->connectMQTT($mqttConfig['listen'], $options);
    }
    
    protected function connectMQTT(string $addr, array $options = [])
    {
        // TODO: Implement connectMQTT() method.
        $this->client = new Client($addr, $options);
        $this->client->onConnect = [$this, 'onMqttConnect'];
//        $this->client->onReconnect =  [$this, 'onMqttReConnect'];
        $this->client->onMessage = [$this, 'onMqttMessage'];
        $this->client->onError = [$this, 'onMqttError'];
        $this->client->connect();
    }

nodejs mqtt 接入代码

const mqtt = require('mqtt')
/* 
  choose which protocol to use for connection here 
*/
// const { connectOptions } = require('./use_mqtt.js')
const { connectOptions } = require('./use_mqtts.js')
// const { connectOptions } = require('./use_ws.js')
// const { connectOptions } = require('./use_wss.js')

/**
 * this demo uses EMQX Public MQTT Broker (https://www.emqx.com/en/mqtt/public-mqtt5-broker), here are the details:
 *
 * Broker host: broker.emqx.io
 * TCP Port: 1883
 * SSL/TLS Port: 8883
 * WebSocket port: 8083
 * WebSocket over TLS/SSL port: 8084
 */

const clientId = 'emqx_nodejs_' + Math.random().toString(16).substring(2, 8)
const options = {
  clientId,
  clean: true,
  connectTimeout: 4000,
  /**
   * By default, EMQX allows clients to connect without authentication.
   * https://docs.emqx.com/en/enterprise/v4.4/advanced/auth.html#anonymous-login
   */
  username: 'emqx_test',
  password: 'emqx_test',
  reconnectPeriod: 1000,
  // Enable the SSL/TLS, whether a client verifies the server's certificate chain and host name
  rejectUnauthorized: true,
  // for more options and details, please refer to https://github.com/mqttjs/MQTT.js#mqttclientstreambuilder-options
}

const { protocol, host, port } = connectOptions
/**
 * if protocol is "mqtt", connectUrl = "mqtt://broker.emqx.io:1883"
 * if protocol is "mqtts", connectUrl = "mqtts://broker.emqx.io:8883"
 * if protocol is "ws", connectUrl = "ws://broker.emqx.io:8083/mqtt"
 * if protocol is "wss", connectUrl = "wss://broker.emqx.io:8084/mqtt"
 *
 * for more details about "mqtt.connect" method & options,
 * please refer to https://github.com/mqttjs/MQTT.js#mqttconnecturl-options
 */
let connectUrl = `${protocol}://${host}:${port}`
if (['ws', 'wss'].includes(protocol)) {
  // mqtt: MQTT-WebSocket uniformly uses /path as the connection path,
  // which should be specified when connecting, and the path used on EMQX is /mqtt.
  connectUrl += '/mqtt'
}

/**
 * If you are using mutual (two-way) TLS authentication, you need to pass the CA, client certificate, and client private key.
 */
if (['mqtts', 'wss'].includes(protocol)) {
  options['ca'] = fs.readFileSync('./path/to/your/ca.crt')
  options['key'] = fs.readFileSync('./path/to/your/client.key')
  options['cert'] = fs.readFileSync('./path/to/your/client.crt')
}

const client = mqtt.connect(connectUrl, options)

const topic = '/nodejs/mqtt'
const payload = 'nodejs mqtt test'
// https://github.com/mqttjs/MQTT.js#qos
const qos = 0

// https://github.com/mqttjs/MQTT.js#event-connect
client.on('connect', () => {
  console.log(`${protocol}: Connected`)

  // subscribe topic
  // https://github.com/mqttjs/MQTT.js#mqttclientsubscribetopictopic-arraytopic-object-options-callback
  client.subscribe(topic, { qos }, (error) => {
    if (error) {
      console.log('subscribe error:', error)
      return
    }
    console.log(`${protocol}: Subscribe to topic '${topic}'`)
    // publish message
    // https://github.com/mqttjs/MQTT.js#mqttclientpublishtopic-message-options-callback
    client.publish(topic, payload, { qos }, (error) => {
      if (error) {
        console.error(error)
      }
    })
  })
})

// https://github.com/mqttjs/MQTT.js#event-reconnect
client.on('reconnect', (error) => {
  console.log(`Reconnecting(${protocol}):`, error)
})

// https://github.com/mqttjs/MQTT.js#event-error
client.on('error', (error) => {
  console.log(`Cannot connect(${protocol}):`, error)
})

// https://github.com/mqttjs/MQTT.js#event-message
client.on('message', (topic, payload) => {
  console.log('Received Message:', topic, payload.toString())
})

相关推荐

最全的MySQL总结,助你向阿里“开炮”(面试题+笔记+思维图)

前言作为一名编程人员,对MySQL一定不会陌生,尤其是互联网行业,对MySQL的使用是比较多的。对于求职者来说,MySQL又是面试中一定会问到的重点,很多人拥有大厂梦,却因为MySQL败下阵来。实际上...

Redis数据库从入门到精通(redis数据库设计)

目录一、常见的非关系型数据库NOSQL分类二、了解Redis三、Redis的单节点安装教程四、Redis的常用命令1、Help帮助命令2、SET命令3、过期命令4、查找键命令5、操作键命令6、GET命...

netcore 急速接入第三方登录,不看后悔

新年新气象,趁着新年的喜庆,肝了十来天,终于发了第一版,希望大家喜欢。如果有不喜欢看文字的童鞋,可以直接看下面的地址体验一下:https://oauthlogin.net/前言此次带来得这个小项目是...

精选 30 个 C++ 面试题(含解析)(c++面试题和答案汇总)

大家好,我是柠檬哥,专注编程知识分享。欢迎关注@程序员柠檬橙,编程路上不迷路,私信发送以下关键字获取编程资源:发送1024打包下载10个G编程资源学习资料发送001获取阿里大神LeetCode...

Oracle 12c系列(一)|多租户容器数据库

作者杨禹航出品沃趣技术Oracle12.1发布至今已有多年,但国内Oracle12C的用户并不多,随着12.2在去年的发布,选择安装Oracle12c的客户量明显增加,在接下来的几年中,Or...

flutter系列之:UI layout简介(flutter-ui-nice)

简介对于一个前端框架来说,除了各个组件之外,最重要的就是将这些组件进行连接的布局了。布局的英文名叫做layout,就是用来描述如何将组件进行摆放的一个约束。在flutter中,基本上所有的对象都是wi...

Flutter 分页功能表格控件(flutter 列表)

老孟导读:前2天有读者问到是否有带分页功能的表格控件,今天分页功能的表格控件详细解析来来。PaginatedDataTablePaginatedDataTable是一个带分页功能的DataTable,...

Flutter | 使用BottomNavigationBar快速构建底部导航

平时我们在使用app时经常会看到底部导航栏,而在flutter中它的实现也较为简单.需要用到的组件:BottomNavigationBar导航栏的主体BottomNavigationBarI...

Android中的数据库和本地存储在Flutter中是怎样实现的

如何使用SharedPreferences?在Android中,你可以使用SharedPreferencesAPI来存储少量的键值对。在Flutter中,使用Shared_Pref...

Flet,一个Flutter应用的实用Python库!

▼Flet:用Python轻松构建跨平台应用!在纷繁复杂的Python框架中,Flet宛如一缕清风,为开发者带来极致的跨平台应用开发体验。它用最简单的Python代码,帮你实现移动端、桌面端...

flutter系列之:做一个图像滤镜(flutter photo)

简介很多时候,我们需要一些特效功能,比如给图片做个滤镜什么的,如果是h5页面,那么我们可以很容易的通过css滤镜来实现这个功能。那么如果在flutter中,如果要实现这样的滤镜功能应该怎么处理呢?一起...

flutter软件开发笔记20-flutter web开发

flutterweb开发优势比较多,采用统一的语言,就能开发不同类型的软件,在web开发中,特别是后台式软件中,相比传统的html5开发,更高效,有点像c++编程的方式,把web设计出来了。一...

Flutter实战-请求封装(五)之设置抓包Proxy

用了两年的flutter,有了一些心得,不虚头巴脑,只求实战有用,以供学习或使用flutter的小伙伴参考,学习尚浅,如有不正确的地方还望各路大神指正,以免误人子弟,在此拜谢~(原创不易,转发请标注来...

为什么不在 Flutter 中使用全局变量来管理状态

我相信没有人用全局变量来管理Flutter应用程序的状态。毫无疑问,我们的Flutter应用程序需要状态管理包或Flutter的基本小部件(例如InheritedWidget或St...

Flutter 攻略(Dart基本数据类型,变量 整理 2)

代码运行从main方法开始voidmain(){print("hellodart");}变量与常量var声明变量未初始化变量为nullvarc;//未初始化print(c)...