大华nodejs http api

it2023-11-02  86

场景

大华摄像头想实现控制代码,搜索来了很多,找到了官网的sdk,无奈支持 linux、windows,使用mac不能使用lib、dll 库。 终于找到了 https://github.com/nayrnet/node-dahua-api 但是已经是很久之前的了,clone下来本地下载下来发现无法使用,高版本nodejs直接就报错,使用nvm 切换到10.16.3才运行起来。 尝试连接线上的摄像头发现无法使用,返回 401 鉴权不通过。

代码自己更改

通过issues 找到了回复 https://github.com/nayrnet/node-dahua-api/issues/9 最先版本的大华摄像头不能使用basic认证了,修改为了Digiestdigiest相关参考 https://www.cnblogs.com/lsdb/p/10621940.html

basic和digest区别

basic 认证形式

请求示例如下,主要是Authorization头(位置不重要,http头一般都不分先后)

GET /GetDeviceInfo HTTP/1.1 Host: 192.168.220.128 Authorization: Basic YWRtaW46MTIzNDU2 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:60.0) Gecko/20100101 Firefox/60.0 Accept-Encoding: gzip, deflate Accept: */* Cache-Control: no-cache Cookie: Secure Connection: close

basic认证方式(前边请求Authorization头的YWRtaW46MTIzNDU2,实际上是用户名admin密码123456使用以下计算方法得到)

var authHeader = "Basic " + new Buffer(options.user + ":" + options.pass).toString("base64");

gigest 认证形式

请求直接返回401 内容为

// 主要需要的内容是 WWW-Authenticate 服务器根据请求自动生成的 const r = [ "HTTP/1.1 401 Unauthorized", 'WWW-Authenticate: Digest realm="Login to f5c8557f6bee8548b0d72a1c30780e5e", qop="auth", nonce="1189347886", opaque="d6ae186150aa54aa82e816e281102905e064907f"', "Connection: close", "Set-Cookie:secure; HttpOnly", "CONTENT-LENGTH: 0", "", "", ];

拿到返回值 根据特定组合编码放置下次请求 Authorization

GET /GetDeviceInfo HTTP/1.1 Host: 192.168.220.128 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:60.0) Gecko/20100101 Firefox/60.0 Authorization: Digest username="admin",realm="TVT API Test Tool",nonce="d4f95e85dc5a39a4914db61b67878f5b",uri="GetDeviceInfo",algorithm="MD5",cnonce="d4f95e85dc5a39a4914db61b67878f5b",nc=00000001,qop="auth",response="1cc4cf126d3c4a70d2de34c5d8c2943c" Accept-Encoding: gzip, deflate Accept: */* Cache-Control: no-cache Cookie: Secure Connection: close // 注意 - 是返回值有的 - XXX 是需要自己处理的值 // username----系统用户名;客户端自行填充 - admin // realm----领域;服务端通过WWW-Authenticate头返回内容可以自己随便定,但其目的是用于提示客户端当前是什么系统,所以规范来说应类似于“myhost@testrealm.com”的形式。 - // nonce----服务端通过WWW-Authenticate头返回的随机数 - // uri----请求接口或资源(似乎规范来说应用GET或POST后的一样,上边例子中少了/是因为服务端没按规范实现)- GetDeviceInfo // algorithm----后边response用的计算方法 - MD5 // cnonce----client nonce,客户端生成的随机数 - uuid // nc----nonce count,用于标识进行请求的次数。(但你一直不变服务端也不会管你对不对)- 00000001 // qop----quality of protection,进一步限定response的计算方法,服务端通过WWW-Authenticate头返回。 - // response----认证最主要的值,前面各字段除algorithm外全要参与该值的计算。

认证计算方式

在最开始的RFC 2069中规定response计算方法如下:

HA1 = MD5(username:realm:password) HA2 = MD5(method:uri) response = MD5(HA1:nonce:HA2)

随后的RFC 2617对计算方法进行了增强,规定计算方法如下(当algorithm值为MD5或未指定、qop未指定时等同RFC 2069

# HA1部分 # 当algorithm值为"MD5"或未指定时,HA1计算方法如下 HA1 = MD5(username:realm:password) # 当algorithm值为"MD5-sess"时,HA1计算方法如下 HA1 = MD5(MD5(username:realm:password):nonce:cnonce) # HA2部分 # 当qop值为"auth"或未指定时,HA2计算方法如下 HA2 = MD5(method:uri) # 当qop值为"auth-int"时,HA2计算方法如下;entityBody是指整个body(?) HA2 = MD5(method:uri:MD5(entityBody)) # response部分 # 当qop值为"auth""auth-int"时,response计算方法如下 response = MD5(HA1:nonce:nonceCount:cnonce:qop:HA2) # 当qop未指定时,response计算方法如下 response = MD5(HA1:nonce:HA2)

nodejs 编写测试文件 测试组合

/* * @Author: 周凯 * @Date: 2020-10-20 14:31:24 * @LastEditTime: 2020-10-20 15:46:06 */ const r = [ "HTTP/1.1 401 Unauthorized", 'WWW-Authenticate: Digest realm="Login to f5c8557f6bee8548b0d72a1c30780e5e", qop="auth", nonce="1189347886", opaque="d6ae186150aa54aa82e816e281102905e064907f"', "Connection: close", "Set-Cookie:secure; HttpOnly", "CONTENT-LENGTH: 0", "", "", ]; const md5 = require("md5"); const { v4 } = require("uuid"); const data = r[1]; console.log(generateAuthorization(data, "admin", "a12345678")); function generateAuthorization(data, username, password) { // 最终结果值 const endParams = { realm: "", qop: "", nonce: "", opaque: "", cnonce: v4().replace(/-/gi, ""), nc: "00000001", }; console.log("data", data); console.log("\n"); const rr = data.replace("WWW-Authenticate", "Authenticate"); rr.split(",").forEach((item) => { let r = item.trim().replace(/=/gi, ":"); if (foramtParams(r, "realm:")) endParams.realm = foramtParams(r, "realm:"); if (foramtParams(r, "qop:")) endParams.qop = foramtParams(r, "qop:"); if (foramtParams(r, "nonce:")) endParams.nonce = foramtParams(r, "nonce:"); if (foramtParams(r, "opaque:")) endParams.opaque = foramtParams(r, "opaque:"); }); /** * 获取参数时的处理 * @param {*} data 数据 * @param {*} val 选择搜索的值 */ function foramtParams(data, val) { if (data.indexOf(val) !== -1 || data.indexOf(val) === 0) { return data.substring(data.indexOf(val) + val.length).replace(/"/gi, ""); } return false; } // HA1部分 const HA1 = md5(`${username}:${endParams.realm}:${password}`); // HA2 const HA2 = md5("GET:GetDeviceInfo"); const response = md5( `${HA1}:${endParams.nonce}:${endParams.nc}:${endParams.cnonce}:${endParams.qop}:${HA2}` ); const returnValidationHeader = `Authorization: Digest username="${username}",realm="${endParams.realm}",nonce="${endParams.nonce}",uri="GetDeviceInfo",algorithm="MD5",cnonce="${endParams.cnonce}",nc="${endParams.nc}",qop="${endParams.qop}",response="${response}"`; return returnValidationHeader; }

更多查看

直接下载npm包使用 npm install node-dahua-api-zhoukai 仓库地址 https://gitee.com/EightDoor/dahua-api-news demo 示例: https://gitee.com/EightDoor/dahua-api-demo大华技术白皮书大华api文档

结语

功能目前实现了 摄像头的控制(ptzMove),其他部分正在不停完善中

感谢

https://github.com/nayrnet/node-dahua-api
最新回复(0)