通过 webrtc 的 signal 服务器 VerneMQ 的搭建 我们已经安装了VerneMQ 服务,接下来就是设置校验了。vernemq 有三种权限校验的方式: 我们接下来一个一个用:
https://vernemq.com/docs/configuration/file-auth.html
1 密码文件校验的方式:
要开启密码校验的方式,就要修改 vernemq.conf 的配置文件, 将以下的配置改成:
1 | allow_anonymous = off |
ps: 注意 plugins.vmq_acl = on 这个也要开启,不然会处于一直连接中的状态,这个是因为 密码文件校验只能校验 auth,也就是能不能连接上。而 acl 校验是校验用户连上之后是否能sub 或者 pub, 所以这个也要开。其实就是关闭连接无校验方式,然后开启密码文件连接校验方式,同时将 密码校验的间隔设置为 10s,接下来我们就通过这个自带的 密码文件管理的 工具来生成一个密码校验
1 | vmq-passwd [-c | -D] passwordfile username |
具体的操作log如下:
1 | [root@VM_156_200_centos vernemq]# vmq-passwd -c vmq.passwd zach |
从上面可以看到,我们往 vmq.passwd (这个就是上面配置项所指定的密码文件,第一次创建,如果不存在,执行会自动创建),然后指定用户名,本例就是zach,接下来就会让你输入密码,而且要确认一遍。然后就成功(本例的密码就是 123456)。
打开 vmq.passwd 来看,可以看到 zach 后面跟了一堆的密码串(这个就是刚才输入的密码的加密串),然后重启了一下 vernemq:
1 | [root@VM_156_200_centos vernemq]# vernemq restart |
接下来我们用测试程序跑下:
1 | var mqtt = require('mqtt'); |
本来如果是直接开启无校验模式的话, 是可以跑成功的:
1 | F:\airdroid_code\nodejs\mqtt>node app.js |
一旦开启密码文件校验方式的话,那么就会报错了:
1 | F:\airdroid_code\nodejs\mqtt>node app.js |
报了一个没有权限的错误,如果加上用户名和密码的话:
1 | var client = mqtt.connect('ws://kebingzao.com:1889/mqtt',{ |
那么就可以连接上
1 | F:\airdroid_code\nodejs\mqtt>node app.js |
当然如果是密码错误,或者用户名错误的话,也都会报错:
1 | var client = mqtt.connect('ws://kebingzao.com:1889/mqtt',{ |
执行结果如下:
1 | F:\airdroid_code\nodejs\mqtt>node app.js |
如果是用户名错的话:
1 | var client = mqtt.connect('ws://kebingzao.com:1889/mqtt',{ |
1 | F:\airdroid_code\nodejs\mqtt>node app.js |
所以密码文件校验,就是这样了。
当然也可以创建多个用户,就是不用-c, 那么就会添加在后面
1 | [root@VM_156_200_centos vernemq]# cat vmq.passwd |
这样就有两个用户了
2 acl 文件校验的方式:
一样修改原来的几个配置
1 | allow_anonymous = off |
安装 vernemq 的时候,默认也有一个 vmq_acl.conf 不过打开是topic #,即默认所有主题,我们要自己配,但是我试着配置了一下 这个文件,但是好像还是连接不上:
1 | [root@VM_156_200_centos vernemq]# cat vmq.acl |
修改之前的测试代码:
1 | var client = mqtt.connect('ws://kebingzao.com:1889/mqtt',{ |
运行结果:
1 | F:\airdroid_code\nodejs\mqtt>node app.js |
这个就蛋疼了,也不知道问题出现在哪里???
后面终于找到原因了,原来 allow_anonymous = on 一定要设置为 on, 因为这个要允许用户连上来才行,接下来才能进行权限的判断
对于本例来说,当用户连接上来,再进行 pub 和 sub 权限的判断
如果这样写的话:
1 | var client = mqtt.connect('ws://kebingzao.com:1889/mqtt',{ |
那么 username 和 topic presenc 都在 acl 文件里面,所以可以成功。
1 | F:\airdroid_code\nodejs\mqtt>node app.js |
但是如果用户名错的话,就会出现权限问题,表现就在能连接上,但是没有权限 pub
1 | var client = mqtt.connect('ws://kebingzao.com:1889/mqtt',{ |
执行结果如下:
1 | F:\airdroid_code\nodejs\mqtt>node app.js |
查看服务器的log如下:
1 | 2018-05-10 20:33:53.404 [warning] <0.657.0>@vmq_websocket:handle_fsm_return:131 ws session stopped abnormally due to 'publish_not_authorized_3_1_1' |
如果是用户名对,但是 topic 不对的话, 也是一样:
1 | var client = mqtt.connect('ws://kebingzao.com:1889/mqtt',{ |
1 | F:\airdroid_code\nodejs\mqtt>node app.js |
当然,这个 acl 文件还可以设置为 允许哪个topic 可以被订阅和被pub
ps: 注意,如果把 #vmq_acl.acl_file = /etc/vernemq/vmq.acl 注释掉,那么就会取/etc/vernemq/vmq.acl 这个默认地址的文件,而如果里面的内容是 topic #
那么就是所有的主题都可以被sub 和 pub了,这样就相当于打开空门了, 所以要谨慎。
而且这边有个很蛋疼的问题,如果要恢复原来的没有任何的校验的情况的话,那么就要设置为:
1 | allow_anonymous = on |
其实这种情况,不是没有任何校验,还是属于acl校验的方式,只不过默认的 vmq.acl 的内容的 topic #, 即允许所有的主题都可以被pub和sub。
所以仅仅只是开启 allow_anonymous = on 还不行, vmq_acl 也要开着,不然会出现可以连得上,但是却没有权限 publish 的情况,不然服务端会报以下错误:
1 | 2018-05-10 13:49:27.096 [error] <0.433.0>@vmq_mqtt_fsm:auth_on_publish:662 can't auth publish [undefined,{[],<<"mqttjs_76284544">>},0,[<<"presence">>],<<"Hello mqtt">>,false] due to no_matching_hook_found |
3 数据库校验的方式:
有好几种,我们主要是使用 redis 来校验,同样要改几个配置:
1 | allow_anonymous =off |
注意: allow_anonymous 这个设置要设置为 off,不然所有的用户都会被连接上
那么接下来往这个redis 插入一条
1 | SET "[\"\",\"test-client\",\"test-user\"]" "{\"passhash\":\"$2a$12$WDzmynWSMRVzfszQkB2MsOWYQK9qGtfjVpO8iBdimTOjCK/u6CzJK\",\"subscribe_acl\":[{\"pattern\":\"#\"}]}, \"publish_acl\":[{\"pattern\":\"#\"}]} " |

这样就有一条记录了 (subscribe_acl 和 publish_acl 都设置为 #, 那么就说明不限制 pub 和 sub 的权限)
从文档来看,这个 passhash 应该就是 123 加密过后的
最后我们的代码是:
1 | var mqtt = require('mqtt'); |
结果发现报错了??
1 | F:\airdroid_code\nodejs\mqtt>node app.js |
权限校验失败, 那么问题来了, 这权限跑判断校验的逻辑从哪里来的。
查看 console.log 是这样:
1 | 2018-05-11 14:05:56.877 [warning] <0.466.0>@vmq_mqtt_fsm:check_user:548 can't authenticate client {[],<<"test-client">>} due to error |
后面查了一下,发现每次开始运行的时候,都会加载这个脚本 /usr/share/vernemq/lua/auth/redis.lua
1 | 2018-05-11 14:05:50.043 [debug] <0.346.0> Supervisor vmq_diversity_script_sup started vmq_diversity_script_sup_sup:start_link("/usr/share/vernemq/lua/auth/redis.lua") at pid <0.354.0> |
后面看了一下,原来关于数据库权限校验的在在这个 redis.lua 脚本里面的, 看了一下这个脚本的信息:
1 | [root@VM_156_200_centos auth]# cat redis.lua |
虽然 lua 脚本看不太懂,但是大概的意思应该就是,如果auth_on_register 这个方法返回 false,那么应该就是校验失败了。为了校验,那么我们就粗暴的校验一下,直接返回 true 看看,改成这样子:
1 | function auth_on_register(reg) |
然后重新启动一下服务: vernemq restart
然后重新运行一下测试程序:
1 | F:\airdroid_code\nodejs\mqtt>node app.js |
发现可以连接上了,但是没有publish的权限???
1 | 2018-05-11 14:13:45.543 [error] <0.476.0>@vmq_mqtt_fsm:auth_on_publish:662 can't auth publish [<<"test-user">>,{[],<<"test-client">>},0,[<<"presence">>],<<"Hello mqtt">>,false] due to error |
对比了 redis.lua 里面的webhook,那么是不是需要我们自己去实现 auth_on_publish 和 auth_on_subscribe 这两个方法吗?
那我们就自己实现这两个方法看看,也是直接返回 true
1 | function auth_on_register(reg) |
重启一下服务, 然后再试下
1 | F:\airdroid_code\nodejs\mqtt>node app.js |
终于又可以连接上,又可以 pub 和 sub了,但是这样子跟没有开启校验,根本没有什么差别,大家都可以连接。所以接下来还是 修改 auth_on_register 方法, 看看为什么会返回 false,这时候就涉及到调试了,如果我们能在这个lua文件进行调试,并且可以打印出来,那么就可以很快调试了。
举个例子比如我们修改成这样:
1 | function auth_on_register(reg) |
看能不能把参数打出来, 执行一下 node 程序,发现在 console.log 里面都没有打出来???到底是会打在哪里呢???
1 | [root@VM_156_200_centos lua_demo]# cd /var/log/vernemq/ |
在这个log文件里面,有看到这个 erlang.log.2 的更新时间都很快,应该会打一些运行的信息,所以就打开一看,果然发现我们print 的信息,这样就好办了
1 | =INFO REPORT==== 11-May-2018::14:20:50 === |
既然是可以打印了,那么我看下,为什么默认自带的那个 redis.lua 会授权失败, 所以就再修改下
1 | function auth_on_register(reg) |
其实就是加一些log,逻辑都不变的,主要就是判断 有没有从redis 里面取到数据,以及密码的hash匹配正不正确,主要是两个逻辑.然后重启一下服务,然后再执行一下测试程序:
1 | F:\airdroid_code\nodejs\mqtt>node app.js |
报错了,然后看下log
1 | (VerneMQ@127.0.0.1)1> arg :m: ,c:test-client,u:test-user,p:123 |
发现可以从 redis 中取到数据了,但是密码的hash计算校验却不匹配。这个说明我们输入的那个hash,根本就不是原密码123的hash值。为了得到123的hash加密之后,正确的hash值是多少,我这边就用go写了一个版本来计算:
1 | package main |
执行得到的结果是:
1 | $2a$10$AeIjpDhnumqDJEliQMBjCuC1Uls9cc8EvzzMt8La1af5xOA/l4HvK |
因此就将redis 里面的这个改成:

然后再执行一下,测试程序看看,能不能匹配上:
1 | F:\airdroid_code\nodejs\mqtt>node app.js |
发现可以了,我们看看打出来log对不对:
1 | arg :m: ,c:test-client,u:test-user,p:123 |
结果一模一样,所以匹配上了,所以就 auth 校验通过了。所以默认的 redis.lua 其实是没有问题。当然我们这边可以加点print log,会比较好。所以最后改成
1 | [root@VM_156_200_centos auth]# cat redis.lua |
接下来就尝试几种情况的log:
1 首先是正常情况:
1 | var client = mqtt.connect('ws://kebingzao.com:1889/mqtt',{ |
1 | F:\airdroid_code\nodejs\mqtt>node app.js |
log:
1 | (VerneMQ@127.0.0.1)1> arg :m: ,c:test-client,u:test-user,p:123 |
2 少参数的情况,比如少了 username 或者 password的情况:
1 | var client = mqtt.connect('ws://kebingzao.com:1889/mqtt',{ |
果然报错:
1 | F:\airdroid_code\nodejs\mqtt>node app.js |
log:
1 | param error, cannot find username or password |
3 参数对,但是配起来找不到 redis 的情况:
1 | var client = mqtt.connect('ws://kebingzao.com:1889/mqtt',{ |
1 | F:\airdroid_code\nodejs\mqtt>node app.js |
log:
1 | arg :m: ,c:test-client,u:test-user1,p:123 |
4 redis 找到记录了,但是 passhash 匹配不上 的情况:
1 | var client = mqtt.connect('ws://kebingzao.com:1889/mqtt',{ |
1 | F:\airdroid_code\nodejs\mqtt>node app.js |
log:
1 | arg :m: ,c:test-client,u:test-user,p:1234 |
5 还有最后一种情况,就是redis 找到了,并且校验成功了,但是在最后的 cache_insert 会失败,因为cache_insert 方法会对 redis 存放的格式进行校验的, 具体代码如下:
1 | function cache_insert(mountpoint, client_id, username, publish_acl, subscribe_acl) |
举个例子,比如校验 publish_acl 和 subscribe_acl 如果不是 数组格式的话,就会出错。
假设我们把 redis 存放发 publish_acl 变成一个 字符串:
1 | { |
那么在执行测试程序的时候,就会报错:
1 | F:\airdroid_code\nodejs\mqtt>node app.js |
这时候 console.log 就会报这个 格式的错误:
1 | 2018-05-11 15:44:54.969 [error] <0.382.0>@vmq_diversity_script_state:handle_info:171 can't call function auth_on_register with args [{addr,<<"125.77.202.250">>},{port,56262},{mountpoint,<<>>},{client_id,<<"test-client">>},{username,<<"test-user">>},{password,<<"123">>},{clean_session,true}] in "/usr/share/vernemq/lua/auth/redis.lua" due to {assert_error,<<"publish_aclexpects one of ( table nil ), but was a string">>} |
总共就以上5种情况。
虽然我们解决了redis auth 的问题。 但是 关于 sub 和 pub 的权限校验,我们其实还是大开空门的。
1 | function auth_on_publish(reg) |
是直接返回true的。如果我们后面要对 sub 和 pub 主题进行权限控制的话,肯定不能这样做的,后面试着看能不能打印这个参数,所以打印这个:
1 | function auth_on_publish(reg) |
但是发现其实并没有触发到, 难道这两个函数没有用, 后面我就试着去掉了一下:我擦,还真的可以去掉,也就是,我redis.lua 改成这样就可以了
1 | [root@VM_156_200_centos auth]# cat redis.lua |
只要修改 auth_on_register 就可以了, 其他5个webhook 都不需要重新写。那么权限是怎么设置的呢?其实上例之所以可以,是因为我们把 subscribe_acl 和 publish_acl 都设置为 #, 这个就意味着所有主题都可以被 sub 和 pub,所以才不会出现权限的问题。

假设我们只指定 presence 主题可以被sub 和 pub。那么对于本例来说,也是可以的
1 | { |
1 | F:\airdroid_code\nodejs\mqtt>node app.js |
但是如果我如果换成另外一个主题,比如 say
1 | client.on('connect', function () { |
那么就会出问题,就会报权限问题
1 | 2018-05-11 18:01:29.686 [error] <0.1434.0>@vmq_mqtt_fsm:auth_on_publish:662 can't auth publish [<<"test-user">>,{[],<<"test-client">>},0,[<<"say">>],<<"Hello mqtt">>,false] due to error |
当然,他这个 pattern 其实有好几个变量的:
1 | The pattern is a MQTT topic string that can contain MQTT wildcards, but also the template variables %m (mountpoint), %u (username), and %c (client id) which are automatically substituted with the auth data provided. |
比如如果是 %u 就代表用户,那么就是:
1 | { |
那么就要这样写:
1 | client.on('connect', function () { |
这样就可以了
1 | F:\airdroid_code\nodejs\mqtt>node app.js |
如果用户名错误的话,那么就会报权限错误
1 | client.on('connect', function () { |
所以如果要允许开放所有的 sub 和 pub 权限的话,并且又是只能这个 clientid 的 这个用户才能用,那么就要设置成这个:
1 | key: ["","test-client","test-user"] |
当然程序就要这样写:
1 | var client = mqtt.connect('ws://kebingzao.com:1889/mqtt',{ |
也就是说,只要你不告诉 别人你的 clientid 和 username,那么别人即使知道你的 主题, 他也不能 sub 和 pub,所以最后如果要完成 redis 校验的话,就要改两个地方:
一个是 vernemq.conf: 相较于默认的配置,主要改变这几个配置:
1 | allow_anonymous = off |
还有就是 redis.lua 文件:
1 | [root@VM_156_200_centos auth]# cat redis.lua |
最后就是记录以下 redis 的插入操作的语法:
1 | SET "[\"\",\"test-client\",\"test-user\"]" "{\"passhash\":\"$2a$12$WDzmynWSMRVzfszQkB2MsOWYQK9qGtfjVpO8iBdimTOjCK/u6CzJK\",\"subscribe_acl\":[{\"pattern\":\"a/+/c\"}]}" |