物联网架构成长之路(6)-EMQ权限控制

时间:2022-09-23 22:07:26

1. 前言

  EMQTT属于一个比较小众的开源软件,很多资料不全,很麻烦,很多功能都是靠猜测,还有就是看官方提供的那几个插件,了解。

2. 说明

  上一小节的插件 emq_plugin_wunaozai

  文件 emq_plugin_wunaozai.erl

  这个文件就是Hook钩子设计了,里面默认已经有了。比如在 on_client_connected这个函数下增加一行 io:format()打印,那么,对应每个mqtt客户端连接到服务器都会打印这一行。一开始我还以为验证逻辑写在这里,然后通过判断,返回{stop,Client},最后发现不是的。能到这里,是表示已经连接上了。具体的权限验证是在emq_auth_demo_wunaozai.erl这个文件。

  文件 emq_auth_demo_wunaozai.erl

  这个文件check函数改成如下

 check(#mqtt_client{client_id = ClientId, username = Username}, Password, _Opts) ->
io:format("Auth Demo: clientId=~p, username=~p, password=~p~n",
[ClientId, Username, Password]),
if
Username == <<"test">> ->
ok;
true ->
error
end.

  表示mqtt客户端登录到服务器要使用用户名为test。否则无法登录。参考emq_auth_pgsql 和 emq_auth_mysql 并测试,发现这个check会有三种返回结果。

  ok. error. ignore.

  如果是ok就表示验证通过。但是要注意的是,多种组合权限验证的时候。例如,在我准备设计的验证流程是,先判断redis是否存在对应的帐号/密码,如果没有那么就到Postgresql读取判断是否有对应的帐号密码。假使是处于两个插件的话,单其中一个Redis插件返回ok,那么就不再判断pgsql插件验证了。如果插件返回error,同样也不会判断pgsql插件。只有返回ignore,才会再判断后面的插件。

  文件 emq_acl_demo_wunaozai.erl

  这个文件check_acl 函数修改如下

 check_acl({Client, PubSub, Topic}, _Opts) ->
io:format("ACL Demo: ~p ~p ~p~n", [Client, PubSub, Topic]),
io:format("~n == ACL ==~n"),
if
Topic == <<"/World">> ->
io:format("allow"),
allow;
true ->
io:format("deny"),
deny
end.

  表示只可以订阅/World 主题。

  基本跟上面原理相同,主要修改check_acl并判断权限,有3中返回。

  allow. deny. ignore.

3. Redis 连接测试

  主要参考emq_auth_redis 这个插件,写插件之前先安装redis和用redis-cli玩一下emqttd知道的emq_plugin_redis插件。

  为了简单,很多配置都省略的,只留一些基本的

  增加 etc/emq_plugin_wunaozai.config

 ##redis config
wunaozai.auth.redis.server = 127.0.0.1:
wunaozai.auth.redis.pool =
wunaozai.auth.redis.database =
##wunaozai.auth.redis.password =
wunaozai.auth.redis.auth_cmd = HMGET mqtt_user:%u password
wunaozai.auth.redis.password_hash = plain
wunaozai.auth.redis.super_cmd = HGET mqtt_user:%u is_superuser
wunaozai.auth.redis.acl_cmd = HGETALL mqtt_acl:%u

  增加 priv/emq_auth_redis.schema

 %% wunaozai.auth.redis.server
{
mapping,
"wunaozai.auth.redis.server",
"emq_plugin_wunaozai.server",
[
{default, {"127.0.0.1", }},
{datatype, [integer, ip, string]}
]
}. %% wunaozai.auth.redis.pool
{
mapping,
"wunaozai.auth.redis.pool",
"emq_plugin_wunaozai.server",
[
{default, },
{datatype, integer}
]
}. %% wunaozai.auth.redis.database =
{
mapping,
"wunaozai.auth.redis.database",
"emq_plugin_wunaozai.server",
[
{default, },
{datatype, integer}
]
}. %% wunaozai.auth.redis.password =
{
mapping,
"wunaozai.auth.redis.password",
"emq_plugin_wunaozai.server",
[
{default, ""},
{datatype, string},
hidden
]
}. %% translation
{
translation,
"emq_plugin_wunaozai.server",
fun(Conf) ->
{RHost, RPort} =
case cuttlefish:conf_get("wunaozai.auth.redis.server", Conf) of
{Ip, Port} -> {Ip, Port};
S -> case string:tokens(S, ":") of
[Domain] -> {Domain, };
[Domain, Port] -> {Domain, list_to_integer(Port)}
end
end,
Pool = cuttlefish:conf_get("wunaozai.auth.redis.pool", Conf),
Passwd = cuttlefish:conf_get("wunaozai.auth.redis.password", Conf),
DB = cuttlefish:conf_get("wunaozai.auth.redis.database", Conf),
[{pool_size, Pool},
{auto_reconnect, },
{host, RHost},
{port, RPort},
{database, DB},
{password, Passwd}]
end
}. %% wunaozai.auth.redis.auth_cmd = HMGET mqtt_user:%u password
{
mapping,
"wunaozai.auth.redis.auth_cmd",
"emq_plugin_wunaozai.auth_cmd",
[
{datatype, string}
]
}. %% wunaozai.auth.redis.password_hash = plain
{
mapping,
"wunaozai.auth.redis.password_hash",
"emq_plugin_wunaozai.password_hash",
[
{datatype, string}
]
}. %% wunaozai.auth.redis.super_cmd = HGET mqtt_user:%u is_superuser
{
mapping,
"wunaozai.auth.redis.super_cmd",
"emq_plugin_wunaozai.super_cmd",
[
{datatype, string}
]
}. %% wunaozai.auth.redis.acl_cmd = HGETALL mqtt_acl:%u
{
mapping,
"wunaozai.auth.redis.acl_cmd",
"emq_plugin_wunaozai.acl_cmd",
[
{datatype, string}
]
}. %%translation
{
translation, "emq_plugin_wunaozai.password_hash",
fun(Conf) ->
HashValue = cuttlefish:conf_get("wunaozai.auth.redis.password_hash", Conf),
case string:tokens(HashValue, ",") of
[Hash] -> list_to_atom(Hash);
[Prefix, Suffix] -> {list_to_atom(Prefix), list_to_atom(Suffix)};
[Hash, MacFun, Iterations, Dklen] -> {list_to_atom(Hash), list_to_atom(MacFun), list_to_integer(Iterations), list_to_integer(Dklen)};
_ -> plain
end
end
}.

  这个时候,dashboard端,可以看到如下信息:

物联网架构成长之路(6)-EMQ权限控制

  如果遇到特殊情况,有时候,是热加载插件问题,记住 rm -rf _rel && make clean && make 即可

  修改 rebar.confi 增加redis依赖

  $ cat rebar.config

 {deps, [
{eredis, ".*", {git, "https://github.com/wooga/eredis", "master"}},
{ecpool, ".*", {git, "https://github.com/emqtt/ecpool", "master"}}
]}.
{erl_opts, [debug_info,{parse_transform,lager_transform}]}.

  修改 Makefile 增加redis依赖

  增加 include/emq_plugin_wunaozai.hrl 头文件

 -define(APP, emq_plugin_wunaozai).

  复制emq_auth_redis/src/emq_auth_redis_config.erl 这个文件到我们的插件中,然后修改文件名和对应的一些内容。

  -module ...

  -include ...

  keys() -> ...

  为每个文件都加上-include (“emq_plugin_wunaozai.hrl”).

  文件emq_plugin_wunaozai_sup.erl 要在后面增加redis连接池配置。

 -module(emq_plugin_wunaozai_sup).
-behaviour(supervisor).
-include("emq_plugin_wunaozai.hrl"). %% API
-export([start_link/]). %% Supervisor callbacks
-export([init/]). start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []). init([]) ->
{ok, Server} = application:get_env(?APP, server),
PoolSpec = ecpool:pool_spec(?APP, ?APP, emq_plugin_wunaozai_cli, Server),
{ok, { {one_for_one, , }, [PoolSpec]} }.

  创建 emq_plugin_wunaozai_cli.erl 文件, 同样从emq_auth_redis_cli.erl进行复制然后作修改。

  到这里,可以先编译一下看是否通过,由于Erlang语言不是很熟悉,基本每做一步修改,都进行编译,防止语法错误,否则很难检查问题。

  文件emq_plugin_wunaozai_app.erl 进行修改

 -module(emq_plugin_wunaozai_app).

 -behaviour(application).

 -include("emq_plugin_wunaozai.hrl").

 %% Application callbacks
-export([start/, stop/]). start(_StartType, _StartArgs) ->
{ok, Sup} = emq_plugin_wunaozai_sup:start_link(),
if_cmd_enabled(auth_cmd, fun reg_authmod/),
if_cmd_enabled(acl_cmd, fun reg_aclmod/),
emq_plugin_wunaozai:load(application:get_all_env()),
{ok, Sup}. stop(_State) ->
ok = emqttd_access_control:unregister_mod(auth, emq_auth_demo_wunaozai),
ok = emqttd_access_control:unregister_mod(acl, emq_acl_demo_wunaozai),
emq_plugin_wunaozai:unload(). %% 根据具体配置文件 emq_plugin_wunaozai.conf 是否有auth_cmd 或者 acl_cmd 配置项目来动态加载所属模块
reg_authmod(AuthCmd) ->
SuperCmd = application:get_env(?APP, super_cmd, undefined),
{ok, PasswdHash} = application:get_env(?APP, password_hash),
emqttd_access_control:register_mod(auth, emq_auth_demo_wunaozai, {AuthCmd, SuperCmd, PasswdHash}). reg_aclmod(AclCmd) ->
emqttd_access_control:register_mod(acl, emq_acl_demo_wunaozai, AclCmd). if_cmd_enabled(Par, Fun) ->
case application:get_env(?APP, Par) of
{ok, Cmd} -> Fun(Cmd);
undefined -> ok
end.

4. 简单验证一下帐号

  通过上面的简单配置,集成redis模块基本就好了,接下来就是比较重要的业务逻辑判断了。这一步主要是在emq_auth_demo_wunaozai.erl 文件写下帐号密码判断。同理主要还是参考emq_auth_redis.erl

物联网架构成长之路(6)-EMQ权限控制

  以上对应三部分,第一部分是Redis缓存中存在指定的帐号密码,第二部分是进行简单的验证,第三部分是打印的日志,一开始用错误的帐号密码进行登录,后面使用正确的帐号密码进行登录,以上,验证通过,可以通过Redis缓存信息进行帐号密码验证。

  客户端测试工具的话,可以用DashBoard上的WebSocket连接测试,也可以在这里下载 https://repo.eclipse.org/content/repositories/paho-releases/org/eclipse/paho/org.eclipse.paho.ui.app/ ,一个桌面端程序。

  测试的时候,建议用这个桌面端程序,WS连接的那个,有时候订阅不成功也提示订阅成功,会很麻烦。

  同时好像还有一个问题,就是在采用Redis进行验证是,EMQ默认会开启ACL缓存,就是说,一个MQTT设备的一次新Connect,第一次才会去读取ACL,进行判断,后面就不会再进行ACL判断了。在测试时,可以关闭cache, 在./etc/emq.conf 文件下 mqtt.cache_acl = true 改为 mqtt.cache_acl = false ,这样每次pub/sub 都会读取Redis进行ACL判断。这个功能有好有坏,根据业务取舍。https://github.com/emqtt/emqttd/pull/764

  个人想法,如果是安全性要求不高的局域网控制,是可以开启cache_acl的,如果是安全性要求较高的,这个选项就不开启了。这样性能会有所下降,如果是采用传统的关系型数据库进行ACL判断,每次pub/sub信息都会读取数据库,物联网下,可能不太现实,这里我是准备用Redis作为ACL Cache,具体效果怎样,要后面才知道。

  目前我是先搭一下框架,性能优化在后面才会进行考虑。

  下一小结主要对上面进行小结,并提供对应的插件代码

物联网架构成长之路(6)-EMQ权限控制的更多相关文章

  1. 物联网架构成长之路&lpar;7&rpar;-EMQ权限验证小结

    1. 前言 经过前面几小节,讲了一下插件开发,这一小节主要对一些代码和目录结构进行讲解,这些都是测试过程中一些个人经验,不一定是官方做法.而且也有可能会因为版本不一致导致差异. 2. 目录结构 这个目 ...

  2. 物联网架构成长之路&lpar;31&rpar;-EMQ基于HTTP权限验证

    看过之前的文章就知道,我之前是通过搞插件,或者通过里面的MongoDB来进行EMQ的鉴权登录和权限验证.但是前段时间发现,还是通过HTTP WebHook 方式来调用鉴权接口比较适合实际使用.还是实现 ...

  3. 物联网架构成长之路&lpar;33&rpar;-EMQ数据存储到influxDB

    一.前言 时隔一年半,技术变化特别快,学习也要跟上才行.以前写过EMQ数据转存问题,当时用了比较笨的方法,通过写插件的方式,把MQTT里面的数据发送到数据库进行存储.当时也是为了学习erlang和em ...

  4. 物联网架构成长之路&lpar;3&rpar;-EMQ消息服务器了解

    1. 了解 物联网最基础的就是通信了.通信协议,物联网协议好像有那么几个,以前各个协议都有优劣,最近一段时间,好像各大厂商都采用MQTT协议,所以我也不例外,不搞特殊,采用MQTT协议,选定了协议,接 ...

  5. 物联网架构成长之路&lpar;4&rpar;-EMQ插件创建

    1. 说明 以下用到的知识,是建立在我目前所知道的知识领域,以后如果随着知识的拓展,不一定会更新内容.由于不是EMQ公司的人,EMQ的文档又很少,很多知识点都是靠猜的.2. 一些资料 架构设计 htt ...

  6. 物联网架构成长之路&lpar;5&rpar;-EMQ插件配置

    1. 前言 上一小结说了插件的创建,这一节主要怎么编写代码,以及具体流程之类的.2. 增加一句Hello World 修改 ./deps/emq_plugin_wunaozai/src/emq_plu ...

  7. 物联网架构成长之路&lpar;25&rpar;-Docker构建项目用到的镜像1

    0. 前言 现在项目处于初级阶段,按照规划,先构建几个以后可能会用到的Image,并上传到阿里云的Docker仓库.以后博客中用到的Image,大部分都会用到这几个基础的Image,构建一个简单的物联 ...

  8. 物联网架构成长之路&lpar;11&rpar;-Redis缓存主从复制

    1. 说明 在我的物联网平台框架框架中,会用到Redis这个中间件.作为EMQ权限认证的缓存.https://www.cnblogs.com/think-in-java/p/5123884.html ...

  9. 物联网架构成长之路&lpar;40&rpar;-Bladex开发框架入门

    0. 前言 前一小节,讲了如何入门,这里就简单讲一下如何自定义查询和权限控制配置. 1. 配置多租户 如果要启用该表的多租户功能,需要在application.yml 这里配置. 2. 配置模糊匹配 ...

随机推荐

  1. 【bzoj2456】 mode

    http://www.lydsy.com/JudgeOnline/problem.php?id=2456 (题目链接) 只看了一眼,直觉便告诉我这是水题.于是跟某码农打赌说10分钟做出来叫爸爸,结果输 ...

  2. Effective Java 34 Emulate extensible enums with interfaces

    Advantage Disadvantage Enum types Clarity Safety Ease of maintenance. None extensibility Typesafe en ...

  3. MYSQL router 自动均衡负载

    配制文件: /etc/mysqlrouter/mysqlrouter.ini [DEFAULT] logging_folder = /var/log/mysql-router plugin_folde ...

  4. 《深入剖析Tomcat》阅读&lpar;三&rpar;

    这里要介绍下Tomcat的一个重要设计方法,Catalina设计方式. Servlet容器是一个复杂系统,但是,它有三个基本任务,对每个请求,servlet容器会为其完成以下三个操作: 1.创建一个R ...

  5. 寻找单向链表的倒数第k个节点

    题目: 输入一个单向链表,输出这个单向链表的倒数第k个节点 template<class T> class ListNode { public: T Data; ListNode<T ...

  6. Tiny6410之UART裸机驱动

    UART简介: UART(Universal Asynchronous Receiver and Transmitter)通用异步收发器(异步串行通信口),是一种通用的数据通信协议,它包括了RS232 ...

  7. laypage 物理分页与逻辑分页实例

    前言 以下介绍摘自 layui官网laypage layPage 致力于提供极致的分页逻辑,既可轻松胜任异步分页,也可作为页面刷新式分页.自 layui 2.0 开始,无论是从核心代码还是API设计, ...

  8. opencv 图像矫正

    四个坐标系的转换:https://blog.csdn.net/humanking7/article/details/44756073 标定和矫正:https://blog.csdn.net/u0134 ...

  9. Spark学习之第一个程序打包、提交任务到集群

    1.免秘钥登录配置: ssh-keygen cd .ssh touch authorized_keys cat id_rsa.pub > authorized_keys chmod 600 au ...

  10. ViewGroup