mqtt服务端的鉴权

因为工作中遇到了需要通过权限进行mqtt消息的限制的情境,对mqtt鉴权的实现进行了简单的研究。
首先要理解mqtt的鉴权,其实是需要三个方面的控制:

  1. 创建链接的权限,若无此权限,客户端连创建连接的机会都没有
  2. 订阅一个主题的权限,当有用了链接权限,用户可以随意订阅任意主题,这达不到隔离的效果,我们还要对用户能订阅的主题进行限制
  3. 向一个主题发布的权限,同理我们也需要限制用户发送消息的功能

1. mosquito mqtt-broker方案

mosquito 是比较典型的mqtt服务端实现,其实也没有对其做很详细的了解,但在鉴权方案上,mosquito采用的是设置账户密码配置文件方式创建用户,并在ACLs权限列表文件进行特定的主题读写权限的定义。
通过mosquito的命令创建账户密码:

1
# mosquitto_passwd -c /etc/mosquitto/pwd user

根据提示设置密码后,密码文件将会生成,在设置文件.conf中:allow_anonymous设置为否,password_file指向密码文件后,用户生效,此时,连接mosquito服务器的mqtt连接必须携带用户和密码,才能成功创建mqtt链接。在创建连接成功后,客户端是可以随意订阅和发布所有主题的,所以mosquito还需要ACLs文件进行主题发布和订阅的控制。
ACLS文件大概格式:

1
2
3
4
5
6
7
user user01
topic write xxx01
topic read xxx01

user user02
topic write xxx02
topic read xxx02

写好ACLs文件后,在设置文件.conf中添加acl_file属性,指明ACLs为文件的路径,重启mosquito,用户的订阅和发布就被限制住了。
然而,对于完整的系统来说,mqtt不管是连接权限,还是订阅和发布权限,应该是一个动态获取并可以进行修改的,原生的mosquito鉴权固定的权限文件对于这个需求就无法胜任了,于是就有了mosquitto-auth-plugin,这个插件可以根据一个服务器或数据库等多样化的后端提供的信息进行mosquitto权限判断。
这么说非常好,这插件用起来可不方便,因为还需要学习如何配置和编程,也就没仔细研究,也许也不是那么难,只是不喜欢。

2. mosca nodejs服务端方案

mosca 是nodejs实现mqtt服务端的首选方案,官方的文档和示例很好地展示了如何编程,提供了发送mqtt消息常用的设置比如Qos等,还能和数据库或则redis进行联动,而且还能够向mosquito一样作为standalone服务端使用,非常方便。一开始的时候自己搭建了转发服务没问题,现在需要鉴权后却发现没那么容易,找了一番没发现有人的博客或则论坛有详细讲解,终于在github的wiki中看到了Authentication & Authorization页面。
官方是这么描述的:
使用mosca,你可以通过定义三个方法来认证一个客户端

#authenticate

#authorizePublish

#authorizeSubscribe

然后给出了一个例子(简单翻译一下)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 如果用户密码有效,则连接被许可
var authenticate = function(client, username, password, callback) {
var authorized = (username === 'alice' && password.toString() === 'secret');
//存储了一下用户名
if (authorized) client.user = username;
callback(null, authorized);
}

// 在这样的情况下,被认证为"alice"的客户端可以向 /users/alice 主题发布消息
// the username from the topic and verifing it is the same of the authorized user(解释一下他巧妙地通过split()对topic中的用户名判断是否与username相符)
var authorizePublish = function(client, topic, payload, callback) {
callback(null, client.user == topic.split('/')[1]);
}

// 在这样的情况下,被认证为"alice"的客户端可以订阅 /users/alice 主题
// the username from the topic and verifing it is the same of the authorized user
var authorizeSubscribe = function(client, topic, callback) {
callback(null, client.user == topic.split('/')[1]);
}

接着就是启动服务:

1
2
3
4
5
6
7
8
var server = new mosca.Server(settings);
server.on('ready', setup);

function setup() {
server.authenticate = authenticate;
server.authorizePublish = authorizePublish;
server.authorizeSubscribe = authorizeSubscribe;
}

刚开始看的时候还不太理解,这三个函数都不怎么友好,参数多,还有callback,不是那么能直接看懂。但多看几遍其实可以了解,这三个函数正是mqtt鉴权所需要的东西。可以理解成当每个函数中的callback(null,Boolean)执行,鉴权工作就完成了,而其中的Boolean值就是我们可以自定义的变量,是需要脑洞的地方(promise,数据库),而这时会发现三个函数的参数都是实现骚操作非常需要的。
到目前还没能直接进行测试,等有空再上来更新(应该没问题)。

Author: Hobai Riku
Link: https://blog.hobairiku.site/2017/11/25/mqtt-auth/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.