阿里云IoT平台提供了基于MQTT的动态注册服务, 也叫一型一密, 可在设备运行时才分配deviceSecret
或者MQTT建连参数clientId
, userName
和password
, 以避免在产线上对每个设备烧写不同的设备密钥。MQTT形式的动态注册支持不用预先上传设备的DeviceName作为白名单。
API列表
以下是API列表及简要说明 (详见components/dynregmq/aiot_dynregmq_api.h
)
接口名 | 说明 |
---|---|
aiot_dynregmq_init | 初始化dynregmq实例并设置默认参数 |
aiot_dynregmq_setopt | 配置dynregmq实例, 详见dynregmq选项配置说明 |
aiot_dynregmq_deinit | 释放dynregmq实例句柄的资源 |
aiot_dynregmq_send_request | 向dynregmq服务器发送请求 |
aiot_dynregmq_recv | 从dynregmq服务器接收应答 |
免白名单与不免白名单的区别
如果客户可以预先将设备的DeviceName上传到物联网平台做白名单,那么设备通过动态注册获取到的将是设备的DeviceSecret,然后设备再通过ProductKey、DeviceName和DeviceSecret连接物联网平台。设备获取到DeviceSecret之后需要将其持久化存储,设备重启、Reset都不能将DeviceSecret丢弃掉,否则设备无法再次连接物联网平台。
如果客户选择不预先上传设备的DeviceName到物联网平台做白名单,那么设备通过动态注册获取到的将是MQTT连接时使用的username、password、clientID,设备连接时需要将这三个参数传递到物联网平台完成设备认证。同样,设备获取到的username、password和clientID需要在设备上持久化存储。
- 客户在设计时一定要将ProductKey、ProductSecret以某种加密方式存储在设备的Flash或者固件中,然后通过解密方式将其解密后使用,避免攻击者轻易获取到产品的ProductKey和ProductSecret。
- 请务必集成“设备取证”功能,用于识别设备是否是伪造的设备。
例程讲解
现对照
components/dynreg-mqtt/demos/dynregmq_basic_demo.c
例程, 分步骤讲解如何使用API
这个例程演示了用SDK配置dynregmq会话实例的参数, 并发起请求和接收应答, 之后
- 如果接收应答失败了, 销毁实例, 回收资源, 结束程序退出
- 如果接收应答成功, 在
demo_dynregmq_recv_handler()
的应答处理回调函数中, 演示解析获取服务端应答的内容
需要用户关注或修改的部分, 已用 TODO
在注释中标明
设置产品信息与设备标识
例程中的ProductKey、ProductSecret、DeviceName是测试使用的, 客户在运行例程前请替换如下的TODO
部分, 传入客户自己设备的真实参数。
/* TODO: 替换为自己设备的productKey, productSecret和deviceName */
char *product_key = "a13FNxxxxx";
char *product_secret = "y7GSILxxxxxx";
char *device_name = "dynregmq_xxxxxxxxx";
进入程序入口, 给SDK配置全局的底层依赖和日志回调
底层依赖描述了硬件平台的资源使用方式, 比如怎样获取时钟, 分配内存等, 日志回调是用户的函数, SDK有log输出的时候会进入这个函数
int main(int argc, char *argv[])
{
...
...
/* 配置SDK的底层依赖 */
aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile);
/* 配置SDK的日志输出 */
aiot_state_set_logcb(demo_state_logcb);
给动态注册会话配置参数
这些连接参数包括如何建立TLS连接, 服务器地址在哪, 连接后的数据及事件回调函数是什么等等
/* 创建1个dynregmq客户端实例并内部初始化默认参数 */
dynregmq_handle = aiot_dynregmq_init();
if (dynregmq_handle == NULL) {
printf("aiot_dynregmq_init failed\n");
return -1;
}
/* 配置连接的服务器地址 */
aiot_dynregmq_setopt(dynregmq_handle, AIOT_DYNREGMQOPT_HOST, (void *)host);
/* 配置连接的服务器端口 */
aiot_dynregmq_setopt(dynregmq_handle, AIOT_DYNREGMQOPT_PORT, (void *)&port);
/* 配置设备productKey */
aiot_dynregmq_setopt(dynregmq_handle, AIOT_DYNREGMQOPT_PRODUCT_KEY, (void *)product_key);
/* 配置设备productSecret */
aiot_dynregmq_setopt(dynregmq_handle, AIOT_DYNREGMQOPT_PRODUCT_SECRET, (void *)product_secret);
/* 配置设备deviceName */
aiot_dynregmq_setopt(dynregmq_handle, AIOT_DYNREGMQOPT_DEVICE_NAME, (void *)device_name);
/* 配置网络连接的安全凭据, 上面已经创建好了 */
aiot_dynregmq_setopt(dynregmq_handle, AIOT_DYNREGMQOPT_NETWORK_CRED, (void *)&cred);
/* 配置DYNREGMQ默认消息接收回调函数 */
aiot_dynregmq_setopt(dynregmq_handle, AIOT_DYNREGMQOPT_RECV_HANDLER, (void *)demo_dynregmq_recv_handler);
/* 设置用户上下文,该上下文会在 demo_dynregmq_recv_handler 被调用时传回 */
aiot_dynregmq_setopt(dynregmq_handle, AIOT_DYNREGMQOPT_USERDATA, (void *)&demo_info);
/* 配置是否使用免白名单模式 */
aiot_dynregmq_setopt(dynregmq_handle, AIOT_DYNREGMQOPT_NO_WHITELIST, (void *)&nowhitelist);
...
...
向服务器发送服务请求
使用aiot_dynregmq_send_request()
接口, 以上面设置的参数, 向dynregmq服务器发起服务请求
/* 发送动态注册请求 */
res = aiot_dynregmq_send_request(dynregmq_handle);
if (res < STATE_SUCCESS) {
printf("aiot_dynregmq_send_request failed: -0x%04X\n", -res);
return -1;
}
请求发送成功后, 尝试接收服务应答
使用aiot_dynregmq_recv()
接口, 以上面设置的参数, 从dynregmq服务器接收服务应答
/* 接收动态注册请求 */
res = aiot_dynregmq_recv(dynregmq_handle);
if (res < STATE_SUCCESS) {
printf("aiot_dynregmq_recv failed: -0x%04X\n", -res);
return -1;
}
以下日志对应的是白名单模式(需预录入DeviceName)下的服务应答
[1592892252.821][LK-0313] MQTT user calls aiot_mqtt_connect api, connect
establish mbedtls connection with server(host='xxx.aliyuncs.com', port=[443])
success to establish tcp, fd=6
success to establish mbedtls connection, fd = 6(cost 44809 bytes in total, max used 47721 bytes)
[1592892253.193][LK-0313] MQTT connect success in 372 ms
[1592892253.193][LK-0309] pub: /ext/register
[LK-030A] < 7B 22 64 65 76 69 63 65 53 65 63 72 65 74 22 3A | {"deviceSecret":
[LK-030A] < 22 61 31 30 64 63 36 35 35 35 64 39 32 63 64 63 | "a10dc6555d92cdc
[LK-030A] < 64 64 36 34 32 65 30 63 65 34 65 34 63 32 35 64 | dd642e0ce4e4c25d
[LK-030A] < 34 22 2C 22 70 72 6F 64 75 63 74 4B 65 79 22 3A | 4","productKey":
[LK-030A] < 22 61 31 4A 51 63 56 66 7A 73 61 6D 22 2C 22 64 | "a1JQcVfzsam","d
[LK-030A] < 65 76 69 63 65 4E 61 6D 65 22 3A 22 72 65 67 69 | eviceName":"regi
[LK-030A] < 73 74 65 72 5F 6E 6F 72 6D 61 6C 22 7D | ster_normal"}
以下日志对应的是免白名单模式下的服务应答
establish mbedtls connection with server(host='xxx.aliyuncs.com', port=[443])
success to establish tcp, fd=6
success to establish mbedtls connection, fd = 6(cost 44809 bytes in total, max used 47721 bytes)
[1592892483.345][LK-0313] MQTT connect success in 503 ms
[1592892483.345][LK-0309] pub: /ext/regnwl
[LK-030A] < 7B 22 63 6C 69 65 6E 74 49 64 22 3A 22 35 6D 47 | {"clientId":"5mG
[LK-030A] < 64 6D 64 31 57 58 45 41 4C 4A 6E 33 6B 72 73 46 | dmd1WXEALJn3krsF
[LK-030A] < 30 30 30 30 31 31 30 22 2C 22 70 72 6F 64 75 63 | 0000110","produc
[LK-030A] < 74 4B 65 79 22 3A 22 61 31 4A 51 63 56 66 7A 73 | tKey":"a1JQcVfzs
[LK-030A] < 61 6D 22 2C 22 64 65 76 69 63 65 4E 61 6D 65 22 | am","deviceName"
[LK-030A] < 3A 22 72 65 67 69 73 74 65 72 5F 6E 77 6C 32 22 | :"register_nwl2"
[LK-030A] < 2C 22 64 65 76 69 63 65 54 6F 6B 65 6E 22 3A 22 | ,"deviceToken":"
[LK-030A] < 5E 31 5E 31 35 39 32 38 39 32 34 38 33 32 37 37 | ^1^1592892483277
[LK-030A] < 5E 31 31 61 66 37 33 62 37 30 35 61 63 64 62 37 | ^11af73b705acdb7
[LK-030A] < 22 7D | "}
应答接收成功后, 在回调函数中解析应答内容
之前已经用AIOT_DYNREGMQOPT_RECV_HANDLER
选项设置了, SDK收到应答时应进入的数据处理函数是demo_dynregmq_recv_handler()
void demo_dynregmq_recv_handler(void *handle, const aiot_dynregmq_recv_t *packet, void *userdata)
{
switch (packet->type) {
/* TODO: 回调中需要将packet指向的空间内容复制保存好, 因为回调返回后, 这些空间就会被SDK释放 */
case AIOT_DYNREGMQRECV_DEVICEINFO_WL: {
if (strlen(packet->data.deviceinfo_wl.device_secret) >= sizeof(demo_devinfo_wl.device_secret)) {
break;
}
/* 白名单模式, 用户务必要对device_secret进行持久化保存 */
memset(&demo_devinfo_wl, 0, sizeof(demo_devinfo_wl_t));
memcpy(demo_devinfo_wl.device_secret, packet->data.deviceinfo_wl.device_secret,
strlen(packet->data.deviceinfo_wl.device_secret));
}
break;
/* TODO: 回调中需要将packet指向的空间内容复制保存好, 因为回调返回后, 这些空间就会被SDK释放 */
case AIOT_DYNREGMQRECV_DEVICEINFO_NWL: {
if (strlen(packet->data.deviceinfo_nwl.clientid) >= sizeof(demo_devinfo_nwl.conn_clientid) ||
strlen(packet->data.deviceinfo_nwl.username) >= sizeof(demo_devinfo_nwl.conn_username) ||
strlen(packet->data.deviceinfo_nwl.password) >= sizeof(demo_devinfo_nwl.conn_password)) {
break;
}
/* 免白名单模式, 用户务必要对MQTT的建连信息clientid, username和password进行持久化保存 */
memset(&demo_devinfo_nwl, 0, sizeof(demo_devinfo_nwl_t));
memcpy(demo_devinfo_nwl.conn_clientid, packet->data.deviceinfo_nwl.clientid,
strlen(packet->data.deviceinfo_nwl.clientid));
memcpy(demo_devinfo_nwl.conn_username, packet->data.deviceinfo_nwl.username,
strlen(packet->data.deviceinfo_nwl.username));
memcpy(demo_devinfo_nwl.conn_password, packet->data.deviceinfo_nwl.password,
strlen(packet->data.deviceinfo_nwl.password));
}
break;
default: {
}
break;
}
}
动态服务服务应答已经被储存在回调函数的入参packet
中, 在回调中需要将它的内容复制到demo_devinfo_nwl
或者demo_devinfo_wl
, 因为回调返回后, SDK就要将相关的内存空间释放掉了
回到主流程, 打印服务应答的消息内容
aiot_dynregmq_recv()
接口返回后(其中经过了触发并执行demo_dynregmq_recv_handler()
), 服务器应答内容已被保存, 将它们打印出来
/* 把服务应答中的信息打印出来 */
if (nowhitelist == 0) {
printf("device secret: %s\n", demo_devinfo_wl.device_secret);
} else {
printf("clientid: %s\n", demo_devinfo_nwl.conn_clientid);
printf("username: %s\n", demo_devinfo_nwl.conn_username);
printf("password: %s\n", demo_devinfo_nwl.conn_password);
}
实际的业务使用中, 应答内容自然不只是被打印出来, 对其中的设备共享密钥deviceSecret
, 一般会被保存, 然后传给SDK的MQTT接口, 用于建立MQTT长连接. 白名单模式和免白名单模式的MQTT连接配置方式不同, 白名单模式用户需要配置设备的三元组ProductKey
, DeviceName
和DeviceSecret
; 而免白名单模式, 用户需要配置MQTT建连参数clientid
, username
和password
. 具体配置方法可以查看MQTTaiot_mqtt_setopt
接口的配置选项。
销毁会话实例, 释放资源退出
动态注册的主要功能: 获取设备共享密钥(deviceSecret)已经演示完毕, 例程到这里就清理资源, 正常退出了
/* 销毁动态注册会话实例 */
res = aiot_dynregmq_deinit(&dynregmq_handle);
在文档使用中是否遇到以下问题
更多建议
匿名提交