天天看點

我的mqtt協定和emqttd開源項目個人了解(14) - 使用redis插件來實作通路控制

一、工作環境準備

準備好redis server,

http://blog.csdn.net/libaineu2004/article/details/76267836 erlang redis用戶端使用開源項目, https://github.com/wooga/eredis erlang連接配接池, https://github.com/emqtt/ecpool emq使用的是v2.3.5版本, https://github.com/emqtt/emq-relx

我們以插件emq_auth_redis來實作,路徑是/home/firecat/Prj/emq2.0/emq-relx-2.3.5/deps/emq_auth_redis

/home/firecat/Prj/emq2.0/emq-relx-2.3.5/data/loaded_plugins設定自啟動插件

emq_recon. 
emq_modules. 
emq_retainer. 
emq_dashboard. 
emq_auth_redis.      

二、redis資料準備

對照/home/firecat/Prj/emq2.0/emq-relx-2.3.5/deps/emq_auth_redis/README.md說明文檔,往redis資料庫寫入username和password:

格式

HSET mqtt_user:<username> password "password"

指令

[root@localhost src]# ./redis-cli
127.0.0.1:6379> HSET mqtt_user:firecat password "123456"
(integer) 1
127.0.0.1:6379> HSET mqtt_user:lqh password "pass123"
(integer) 1
127.0.0.1:6379> HSET mqtt_user:firecatGTerm password "he2345v11t11"
(integer) 1
127.0.0.1:6379> hgetall mqtt_user
(empty list or set)
127.0.0.1:6379> HMGET mqtt_user:lqh password
1) "pass123"
127.0.0.1:6379> hgetall mqtt_user:lqh
1) "password"
2) "pass123"      

源檔案/home/firecat/Prj/emq2.0/emq-relx-2.3.5/deps/emqttd/src/emqttd_access_control.erl會啟用校驗

%% @doc Authenticate MQTT Client.
-spec(auth(Client :: mqtt_client(), Password :: password()) -> ok | {ok, boolean()} | {error, term()}).
auth(Client, Password) when is_record(Client, mqtt_client) ->
    auth(Client, Password, lookup_mods(auth)).
auth(_Client, _Password, []) ->
    case emqttd:env(allow_anonymous, false) of
        true  -> ok;
        false -> {error, "No auth module to check!"}
    end;
auth(Client, Password, [{Mod, State, _Seq} | Mods]) ->
    case catch Mod:check(Client, Password, State) of
        ok              -> ok;
        {ok, IsSuper}   -> {ok, IsSuper};
        ignore          -> auth(Client, Password, Mods);
        {error, Reason} -> {error, Reason};
        {'EXIT', Error} -> {error, Error}
    end.      

源檔案emq_auth_redis.erl,有校驗的實施過程

check(Client, Password, #state{auth_cmd  = AuthCmd,

                              super_cmd = SuperCmd,

                              hash_type = HashType}) ->

   Result = case emq_auth_redis_cli:q(AuthCmd, Client) of

源檔案emq_auth_redis_cli.erl,有redis的查詢指令實作方法

%% Redis Query.

-spec(q(string(), mqtt_client()) -> {ok, undefined | binary() | list()} | {error, atom() | binary()}).

q(CmdStr, Client) ->

   io:format("1 CmdStr is: ~s  ~n", [CmdStr]),

   Cmd = string:tokens(replvar(CmdStr, Client), " "),

   io:format("2 Cms is: ~s  ~n", [Cmd]),

   ecpool:with_client(?APP, fun(C) -> eredis:q(C, Cmd) end).

ereids的使用案例,

{ok, C} = eredis:start_link().

{ok, <<"OK">>} = eredis:q(C, ["SET", "foo", "bar"]).

{ok, <<"bar">>} = eredis:q(C, ["GET", "foo"]).

KeyValuePairs = ["key1", "value1", "key2", "value2", "key3", "value3"].

{ok, <<"OK">>} = eredis:q(C, ["MSET" | KeyValuePairs]).

{ok, Values} = eredis:q(C, ["MGET" | ["key1", "key2", "key3"]]).

string:tokens的用法可以參考erlang官方文檔,

http://erlang.org/doc/man/string.html

tokens(String, SeparatorList) -> Tokens

Types

String = SeparatorList = string()

Tokens = [Token :: nonempty_string()]

Returns a list of tokens in String, separated by the characters in SeparatorList.

Example:

> tokens("abc defxxghix jkl", "x ").

["abc", "def", "ghi", "jkl"]

三、除了emq_auth_redis_cli.erl上訴的查詢方法,我們自己也可以實作其他查詢方法:

-export([connect/1, q/2, q2/2]).
%% Redis Query.firecat add.
-spec(q2(string(), binary()) -> {ok, undefined | binary() | list()} | {error, atom() | binary()}).
q2(CmdStr, ClientId) ->
    io:format("3 CmdStr is: ~s  ~n", [CmdStr]),
    Cmd = string:tokens(replvar(CmdStr, "%c", ClientId), " "),
    io:format("4 Cms is: ~s  ~n", [Cmd]),
    ecpool:with_client(?APP, fun(C) -> eredis:q(C, Cmd) end).      

這樣,我在其他任意的erlang mod,都可以調用該方法,例如**.erl:

%%%% redis test
redis_test(ClientId) ->
   Result = case emq_auth_redis_cli:q2("HMGET mqtt_clientid:%c user group type", ClientId) of
                {ok, [undefined|_]} ->
                    io:format("clientid ignore: undefined  ~n"), %%firecat
                    {error, undefined};
                {ok, [User, Group, Type]} -> %%[undefined, undefined, undefined]
                    io:format("clientid is: ~s ~s ~s ~n", [User, Group, Type]),
                    ok;
                {error, Reason} ->
                    io:format("clientid error is: ~s  ~n", [Reason]),
                    {error, Reason}
             end,
   case Result of
             ok -> ok; %%firecat
             Error -> ok
             end.      

四、redis插件除了可以檢驗username和password,還可以校驗clientid的合法性。需要手動新增源檔案emq_clientid_redis.erl。完整的源碼請通路:

https://download.csdn.net/download/libaineu2004/10289890