4.1 认证
4.1.1 OTAP 摘要认证过程
客户端程序向设备发送请求时,需要使用摘要认证(详见RFC 7616)完成身份认证。
以下是一个HTTP摘要认证交互过程示例:
GET 172.7.203.11:80/iot/global/0-
global/model/attribute/get/Authentication/UserCheck HTTP/1.1
Host: 10.21.84.147
Connection: Keep-Alive
HTTP/1.1 401 Unauthorized
Content-Type: text/html
Date: Mon, 01 Feb 2021 14:06:08 GMT
Content-Length: 126
Connection: Keep-Alive
WWW-Authenticate: Digest realm="3521781c29acb312330dd668", qop="auth",
nonce="05a5f52a199db1b8:3521781c29acb312330dd668:1775dea3f75:85",
algorithm="SHA-256"
GET 172.7.203.11:80/iot/global/0-
global/model/attribute/get/Authentication/UserCheck HTTP/1.1
Authorization: Digest
username="admin",realm="3521781c29acb312330dd668",nonce="05a5f52a199db1b8:352178
1c29acb312330dd668:1775dea3f75:85",uri="/iot/global/0-
global/model/attribute/get/Authentication/UserCheck",algorithm="SHA-
256",cnonce="76f8f17a6f8ff752ccd10a1edf20e0d7",nc=00000001,qop="auth",response="
f8332ab3a22c7744af510a5558aed1f1",opaque="799d5"
Host: 10.21.84.147
HTTP/1.1 200 OK
Content-Type: application/json; charset="UTF-8"
Date: Mon, 01 Feb 2021 14:06:08 GMT
Content-Length: 243
Connection: Keep-Alive
{
"status": 200,
"code": "0x00000000",
"errorMsg": "Succeeded.",
"data":{
"statusValue": 200,
"statusString": "OK",
"isDefaultPassword": false,
"isRiskPassword": false,
"isActivated": true,
"residualValidity": -3
}
}
在RFC 7616中定义了三种初始摘要算法MD5、SHA-256、SHA-512-256,安全等级依次升高。
我们推荐优先使用安全等级更高的SHA-256和SHA-512-256摘要算法,因为MD5算法存在安全性风险。对于支持多种摘要算法的设备,可以在设备的Web配置页面中切换对应的摘要算法,也可以通过OTAP接口
(/iot/${DEVID}/${CHILDID}/${LOCALINDEX}-${RESOURCETYPE}/model/attribute/get/HTTPAccess/WebCertifacateCfg )切换对应的摘要算法。
目前主流的HTTP请求类库都封装支持了摘要认证。客户程序只需要简单地调用类库接口即可完成摘要认证过程,以下是示例源码。
需要注意的是,有些HTTP请求类库仅支持MD5算法的摘要认证,若服务端未开启MD5摘要认证会导致客户端认证失败。 此时可以舍弃一定的安全性进行向下兼容,开启服务端MD5摘要认证;要么客户端实现更高等级的摘要认证算法,这会增加客户端的开发工作。
4.1.2 C/C++ (libcurl)
// #include <curl/curl.h>
// 回调函数
static size_t OnWriteData(void* buffer, size_t size, size_t nmemb, void* lpVoid)
{
std::string* str = dynamic_cast<std::string*>((std::string *)lpVoid);
if( NULL == str || NULL == buffer )
{
return -1;
}
char* pData = (char*)buffer;
str->append(pData, size * nmemb);
return nmemb;
}
std::string strUrl = "http://192.168.18.84:80/iot/global/0-
global/model/attribute/get/Authentication/UserCheck";
std::string strResponseData;
CURL *pCurlHandle = curl_easy_init();
curl_easy_setopt(pCurlHandle, CURLOPT_CUSTOMREQUEST, "GET");
curl_easy_setopt(pCurlHandle, CURLOPT_URL, strUrl.c_str());
// 设置用户名和密码
curl_easy_setopt(pCurlHandle, CURLOPT_USERPWD, "admin:admin12345");
// 设置认证方式为摘要认证
curl_easy_setopt(pCurlHandle, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
// 设置回调函数
curl_easy_setopt(pCurlHandle, CURLOPT_WRITEFUNCTION, OnWriteData);
// 设置回调函数的参数,获取反馈信息
curl_easy_setopt(pCurlHandle, CURLOPT_WRITEDATA, &strResponseData);
// 接收数据时超时设置,如果5秒内数据未接收完,直接退出
curl_easy_setopt(pCurlHandle, CURLOPT_TIMEOUT, 5);
// 设置重定向次数,防止重定向次数太多
curl_easy_setopt(pCurlHandle, CURLOPT_MAXREDIRS, 1);
// 连接超时,这个数值如果设置太短可能导致数据请求不到就断开了
curl_easy_setopt(pCurlHandle, CURLOPT_CONNECTTIMEOUT, 5);
CURLcode nRet = curl_easy_perform(pCurlHandle);
if (0 == nRet)
{
// 输出接收的消息
std::cout << strResponseData << std::endl;
}
curl_easy_cleanup(pCurlHandle);
4.2 报文解析
4.2.1 报文格式
在使用OTAP进行通讯交互过程中,请求和响应报文通常采用JSON格式的文本数据,也会有一些如固件包、配置文件二进制格式数据,也存在一次请求中包括多种格式数据的表单格式(multipart/formdata)
。
4.2.1.1 JSON
对应HTTP请求Headers中的Content-Type 通常为application/json 。OTAP请求和响应报文中的JSON都为UTF-8编码格式。
4.2.1.2 二进制数据
OTAP over HTTP敏感信息加密时,原JSON报文加密后转变为二进制数据,Headers中的Content-Type 使用Content-Type: application/octet-stream。
4.2.1.3 表单(multipart/form-data)
OTAP协议传输二进制文件(图片、视频、音频、配置文件、固件包等),使用HTTP表单格式,比如向人脸库中添加人脸记录需要同时提交JSON格式人员信息和二进制格式人脸图片。表单格式对应HTTP请求Headers中的Content-Type 通常为multipart/form-data, boundary=AaB03x ,其中boundary是一个变量,用于将整个HTTP Body分割成多个单元,每个单元为一份数据,都有各自的Headers和Body。
表单单元Headers中Content-Disposition 的name 属性表示此表单单元的名称, filename 属性表示此表单单元Body的文件名,每个表单单元都需要设置name 属性,当表单单元Body是一个文件时,需要设置filename 属性。
表单单元Headers中的Content-Length 表示Body的长度,计算时从两个CRLF( \r\n )之后开始,到下一段表单起始的-- 结束(不包括-- ,此处需注意-- 前面应有一个CRLF,此CRLF视作两个表单单元的分隔符,上一个表单单元的Content-Length 的值应不包含此CRLF的长度)。表单详细格式说明参考RFC 1867 (Form-based File Upload in HTML),请注意boundary前面和后面的横线-- 。
说明:
RFC规范中强烈建议携带一个整体的Headers字段Content-Length ,但这不是必须的。而且没有要求每个表单单元Headers中是否应携带Content-Length 字段和Content-Type 字段。客户程序和设备程序在解析表单格式数据时,都应考虑Headers中没有Content-Length 字段和Content-Type 字段的情况。
为了避免boundary的值与报文内容冲突,建议使用较长且复杂的字符串取值,如UUID。
客户端向设备提交的OTAP表单格式数据示例如下。
POST /iot/global/0-global/model/service/operate/FaceLibrary/ImportPictureData
Content-Type: multipart/form-data; boundary=e5c2f8c5461142aea117791dade6414d
Content-Length: 56789
--e5c2f8c5461142aea117791dade6414d
Content-Disposition: form-data; name="PictureUploadData";
Content-Type: application/json
Content-Length: 1234
{
...
}
--e5c2f8c5461142aea117791dade6414d
Content-Disposition: form-data; name="face_picture";
filename="face_picture.jpg";
Content-Type: image/jpeg
Content-Length: 34567
图片数据
--e5c2f8c5461142aea117791dade6414d—
当有多个表单单元时,OTAP存在多个报文文件信息结构,其中的filePath 字段和表单单元的name 关联。
文件信息结构报文示例如下:
{
/*req, object, 文件存储信息, range:[,], desc:*/
"filePathType": "URL",
/*req, string, 文件路径类型, const:, range:[,], enum:[URL#URL, binary#直传
(base64编码), localPath#设备本地存储, simpleStorage#简单存储协议, multipart#表单格式],
format:, pattern:, unit:, desc:simpleStorage#简单存储协议:对应的filePath内容是
storageID。URL#URL:类似:http://storage.com/1.jpg,可以直接访问到文件。localPath#设备
本地存储:文件存储在设备,filepah内容为路径,由设备生成。平台通过专用的文件传输链路向设备获取文
件。binary#直传:filePath的内容为文件的二进制数据。注意传输时需要进行base64转码,转码后整个报
文大小不能超过256K。有可能超过时,都不是适合用该类型。multipart#表单格式:仅在http(https)协
议的情况下支持,filePath的值为表单的name值。*/
"filePath": ""
/*req, string, 文件路径, const:, range:[0,30720], enum:[], format:, pattern:,
unit:, desc:根据filePathType的不同取值,用不同的方式获取文件*/
}
……