mosquitto鉴权插件的开发与说明(一)

1.      mosquitto的插件的接口在文件mosquitto_plugin.h中进行了声明,在创建自己的鉴权插件代码中必须包含头文件mosquitto_plugin.h,并且实现该头文件中声明的所有鉴权接口函数(接口函数mosquitto_log_printf除外)。

2.      mosquitto插件的权限校验方式由头文件mosquitto_plugin.h中声明的接口函数所限制,共提供用户名密码校验、acl列表校验、psk_key校验。

3.      动态库相关的操作宏,在mosquitto代码中的lib_load.h文件中,为了支持跨平台,对动态库的加载、关闭、获取函数进行了统一的宏定义:

#ifdef WIN32

#        defineLIB_LOAD(A) LoadLibrary(A)

#        defineLIB_CLOSE(A) FreeLibrary(A)

#        defineLIB_SYM(HANDLE, SYM) GetProcAddress(HANDLE, SYM)

#else

#        defineLIB_LOAD(A) dlopen(A, RTLD_NOW|RTLD_GLOBAL)

#        defineLIB_CLOSE(A) dlclose(A)

#        defineLIB_SYM(HANDLE, SYM) dlsym(HANDLE, SYM)

#endif

这里统一定义了动态库加载宏:LIB_LOAD,动态库关闭的宏LIB_CLOSE,以及获取动态库中对外导出函数的宏LIB_SYM。

4.      插件相关的参数配置

      Mosquito的配置文件mosquitto.conf中为插件的参数做了专门说明:如果要使用插件,则必须启用配置参数auth_plugin,并通过此参数指定插件动态库的位置,在配置文件中可以仿照如下配置:

auth_plugin /usr/lib/auth-plug.so

如果插件中需要用到配置参数,则需要全部以auth_opt_*作为前缀,例如我的插件要redis,那么他们的参数就可以在配置文件中这样配置插件所需要的redis参数:

auth_opt_redis_host 192.168.2.154

auth_opt_redis_port 6379

auth_opt_redis_pass 123456

在mosquitto加载配置的时候,会将这些配置参数(以auth_opt_为前缀的参数)加载到struct mqtt3_config 结构体变量的auth_options成员中,auth_options被定义成struct mosquitto_auth_opt类型的数组,通过structmqtt3_config 结构体变量另一个成员auth_option_count来记录数组的长度,而结构体struct mosquitto_auth_opt就是一个key-value结构,如下所示:

structmosquitto_auth_opt {

char *key;

char *value;

};

如此以来,在程序启动的时候,所有插件需要的配置参数都会被加载到程序内存,并且在插件的初始化时(对应插件的接口函数mosquitto_auth_plugin_init)将配置文件数组auth_options以及数组长度auth_option_count传递给插件。

【注意】 mosquitto在传递配置参数名和值给插件时,不传递参数名前缀auth_opt_,例如我们在配置文件mosquitto.conf中配置了参数auth_opt_redis_port 6379,我们在初始化插件时拿到的key实际上是redis_host,而前缀auth_opt_则不会被传入到插件中。

5.      Mosquito对插件的使用方式

      Mosquito在main函数启动的时候会调用封装的函数mosquitto_security_module_init来初始化插件,在mosquitto退出的时候会执行mosquitto_security_module_cleanup函数,在该函数中会调用插件的资源释放函数mosquitto_auth_plugin_cleanup来释放插件中申请的资源,例如连接之类。

      在mosquitto代码中,对插件的加载、检测操作主要在文件security.c中的函数mosquitto_security_module_init里进行,它主要做了如下事情:

(1)      判断配置参数auth_plugin中是否提供了插件的全名,如果是,则通过宏LIB_LOAD加载插件的动态库;

(2)      加载动态库之后,立即调用插件接口函数mosquitto_auth_plugin_version获取插件的版本,并对插件版本进行检测,看其值是否为:MOSQ_AUTH_PLUGIN_VERSION,如果不是,则关闭动态库并返回失败;

(3)      检测接口函数是否实现,通过读取插件动态库里面的mosquitto_auth_plugin_init函数、mosquitto_auth_plugin_cleanup函数、mosquitto_auth_security_init函数、mosquitto_auth_security_cleanupt函数、mosquitto_auth_acl_check函数、mosquitto_auth_unpwd_check函数、mosquitto_auth_psk_key_get函数地址,并将这些函数的地址放在结构体成员db->auth_plugin的各函数指针中,如果没有找到这写函数中的任何一个则关闭动态库,返回失败;

(4)      初始化动态库,通过调用db->auth_plugin.plugin_init,也就是第(3)步mosquitto_auth_plugin_init对插件动态库进行初始化操作;

(5)      Mosquito在文件security.c中对插件所提供的全部进行了封装,例如:mosquitto_security_cleanup函数封装了插件提供mosquitto_auth_plugin_cleanup函数、mosquitto_security_init函数对插件的mosquitto_auth_security_init函数进行了封装;mosquitto_security_cleanup函数对插件中提供的mosquitto_auth_security_cleanup函数进行了封装、函数mosquitto_acl_check对插件提供的mosquitto_auth_acl_check函数进行了封装。。。。。。

6.      插件数据交互

      Mosquitto关于插件的相关结构体的定义如下(此结构体的定义在文件mosquitto_broker.h中):

struct _mosquitto_auth_plugin{

      void*lib;

      void*user_data;

      int(*plugin_version)(void);

      int(*plugin_init)(void **user_data, struct mosquitto_auth_opt *auth_opts, intauth_opt_count);

      int(*plugin_cleanup)(void *user_data, struct mosquitto_auth_opt *auth_opts, intauth_opt_count);

      int(*security_init)(void *user_data, struct mosquitto_auth_opt *auth_opts, intauth_opt_count, bool reload);

      int(*security_cleanup)(void *user_data, struct mosquitto_auth_opt *auth_opts, intauth_opt_count, bool reload);

      int(*acl_check)(void *user_data, const char *clientid, const char *username, constchar *topic, int access);

      int(*unpwd_check)(void *user_data, const char *username, const char *password);

      int(*psk_key_get)(void *user_data, const char *hint, const char *identity, char*key, int max_key_len);

};

       需要关注的是指针void*user_data,该用于在插件的各接口之间传递数据,在开发自己的mosquitto插件的时候,需要在实现插件的初始化接口函数mosquitto_auth_plugin_init的代码中中创建一个自己的内存空间(例如,可以创建自己的结构体),然后将该结构体的首地址传送给mosquitto,在后续的每个鉴权函数的接口中的第一个参数就是这个地址,如下以用户名和密码校验举例子更容易说明问题:

      假如我们的一些信息存储到了数据库,在鉴权插件中,我们需要从数据库中读取用户名和密码,然后对客户端传递过来的用户名和密码进行校验,这时,我们可以在插件初始化的时候创建和数据库的连接,然后将连接信息放在自定义的结构体中,把这个结构体通过插件初始化函数mosquitto_auth_plugin_init的参数void**user_data传递给mosquitto,后续校验函数mosquitto_auth_unpwd_check中,只需要从该结构体地址中获取到连接信息,就可以直接从数据库中读取数据了,而不用每次校验都要跟数据库建立连接。

7.      插件接口函数说明:

(1)      mosquitto_log_printf,工具类接口,即你可以定义自己的日志输出函数来代替mosquitto代码里的日志输出;这个接口函数不是必须实现的。

(2)      接口函数mosquitto_auth_plugin_version

      mosquitto在加载插件之后,将立即调用此接口函数来检测自己是否支持该版本的插件,在自己实现改接口函数时只需(也是必须)返回宏:MOSQ_AUTH_PLUGIN_VERSION,这个宏也在头文件mosquitto_plugin.h中进行了定义,它是一个整形数值,在1.4.11版本的mosquitto中该宏的定义形式为:

#define MOSQ_AUTH_PLUGIN_VERSION 2

(3)      接口函数mosquitto_auth_plugin_init

      插件初始化函数,在实现此此接口函数时可以初始化用户自己开发的插件,需要注意其第一个参数void **user_data,如果需要在各插件接口之间共享数据的时候,就需要在插件中申请堆内存,然后把要共享的数据放在此内存中,并借助此参数传递所申请的堆内存地址传递给mosquitto,mosquitto后续调用各插件接口函数时,会传递该地址给各插件的接口函数。

(4)      接口函数mosquitto_auth_plugin_cleanup

      插件的清理函数,在被封装到了函数mosquitto_security_module_cleanup中,在mosquitto退出的时候会调用这个函数(main函数中)。如果在开发插件的时候申请了资源,就需要在实现这个接口函数时进行释放,例如,创建的连接等。【注意】这个接口函数不要与mosquitto_auth_security_cleanup混淆,而且mosquitto_auth_security_cleanup一定要在这个接口函数之前调用!

(5)      接口函数mosquitto_auth_security_init

      插件关于安全相关的初始化,该接口函数被封装在函数mosquitto_security_init中进行调用;【注意】该函数的第三个参数reload,如果传入的值为false则表明是第一次调用,如果是true,则表明它是mosquitto收到了重新加载配置的信号之后,正在进行重新加载配置;

(6)      mosquitto_auth_security_cleanup

      插件安全相关的资源清理函数,被封装在函数mosquitto_security_cleanup中进行了调用,在mosquitto退出运行的时候调用;另外,在重新加载配置文件的时候也会调用此函数,然后再调用mosquitto_auth_security_init;

(7)      mosquitto_auth_acl_check

      该函数主要对用户(即连接)访问某个topic进行控制,可以对某个topic的读权限(对应mqtt协议的sub操作)进行控制,也可以对写权限控制控制(写权限对应mqtt协议的sub操作),它被封装到了函数mosquitto_acl_check中,在有订阅/发布操作时将被执行用以检查当前操作用户(连接)的权限。调用此函数时需提供连接id,用户名等但是,这里要注意参数access表示校验的权限类型,宏定义如下:

#defineMOSQ_ACL_NONE 0x00

#defineMOSQ_ACL_READ 0x01

#defineMOSQ_ACL_WRITE 0x02

返回宏MOSQ_ERR_SUCCESS表示访问被授权,可以访问该topic,返回MOSQ_ERR_ACL_DENIED表示当前连接不能访问当前topic的读或者写权限。

(8)      mosquitto_auth_unpwd_check

      用户名密码校验,它被封装到了函数mosquitto_unpwd_check中;在mqtt的CONNECT控制报文(对应的类型为:0x10)对应的处理函数mqtt3_handle_connect中进行调用(说明:mqtt协议中,在客户端和mosquitto进行tcp三次握手之后,发送给mosquitto的第一条mqtt协议的消息必须为0x10类型的CONNECT控制报文);mosquitto调用函数mosquitto_unpwd_check对连接进行用户名/密码校验的前提是:在CONNECT类型的MQTT控制报文中如果设置了UserName Flag标志位(在CONNECT控制报文的变长头部)以及用户名和密码。

这里你可以将每个用户以及对应的密码保存到redis或者数据库中,然后通过mosquitto的鉴权插件对连接进来的用户名和密码进行校验。但是这种方法在不使用tls时,几乎起不到对连接安全的控制作用,具体原因可以查看下面这篇分析文章:http://blog.csdn.net/houjixin/article/details/53324379

(9)      mosquitto_auth_psk_key_get

      当客户端和mosquitto之间采用tls/psk进行通信时,将采用此接口函数获取指定客户端的共享密钥,该接口函数被封装到了函数mosquitto_psk_key_get中。

猜你喜欢

转载自blog.csdn.net/hjx_1000/article/details/76590086