目录
优化思考 (摘要认证客户端实现已做优化,优化文章地址见 https://blog.csdn.net/Harvay/article/details/112020107 )
需求背景
开发客户端工具,实现本地PC客户端登录操作海康设备并做相关操作的能力支持,经调研了解有两种实现方式:
1、 C++、Java、C#等传统客户端实现手段结合设备SDK进行操作支持;(该方式支持所有的海康设备接入)
2、 通过Web手段结合Http摘要认证进行实现。(该方式需要设备端支持摘要方式认证方可进行设备接入等操作)
需求方项目上均采用近年海康设备,目前海康主推ISAPI协议方式对接设备,考虑Web技术栈的开发效率以及团队人员技术栈分布情况,采用方式2实现需求方需求。
技术栈
electron + vue 实现客户端, node http + axios 做服务端以及设备对接的http客户端支撑。
实现过程逻辑
摘要认证大致过程:
1、 客户端发去没有认证证书的请求,获取服务端的认证信息反馈:
2、 服务器产生随机数,并将相关信息至于响应中 www-Authenticate字段中, 用于后续客户端获取做进一步认证信息提交:
其中若设备支持多种认证方式,则对应多个www-Authenticate响应头信息。
3、 客户端接收401响应后,选择算法(md5)生成消息摘要,并将摘要信息填入请求头的Authorization字段中,发送服务端校验信息:
4、 服务端接收摘要信息后,选择算法以及掌握的数据,进行校验,校验通过后放行数据。
代码信息
业务代码 ---调用---》 digestReqest js(内部封装各类设备操作接口) --- 调用 ---》digest-auth-client.js (封装digest认证过程工具)
具体代码内容:
digest-auth-client.js
const http = require('http')
const crypto = require('crypto')
const md5 = (data) => {
const md5 = crypto.createHash('md5')
return md5.update(data).digest('hex')
}
export const digestRequest = (options, data, user, pass, cb) => {
return new Promise(function(resolve, reject) {
const req = http.request(options, (res) => {
resolve(res)
})
req.on('error', (e) => {
reject(e)
})
if (data) {
req.write(data)
}
req.end()
}).then(function(res) {
console.log(`STATUS: ${res.statusCode}`)
console.log(`HEADERS: ${JSON.stringify(res.headers)}`)
if (res.statusCode === 401) {
const cnonce = md5(String(new Date().getTime()))
const auth = res.headers['www-authenticate']
let realm, nonce, qop
const authSplit = auth.split(',')
for (const k in authSplit) {
if (authSplit[k].indexOf('realm=') >= 0) {
const realmSplit = authSplit[k].split('="')
realm = realmSplit[realmSplit.length - 1]
realm = realm.substring(0, realm.length - 1)
}
if (authSplit[k].indexOf('nonce=') >= 0) {
const nonceSplit = authSplit[k].split('="')
nonce = nonceSplit[nonceSplit.length - 1]
nonce = nonce.substring(0, nonce.length - 1)
}
if (authSplit[k].indexOf('qop=') >= 0) {
const qopSplit = authSplit[k].split('="')
qop = qopSplit[qopSplit.length - 1]
qop = qop.substring(0, qop.length - 1)
}
}
const HA1 = md5(user + ':' + realm + ':' + pass)
const HA2 = md5(options.method + ':' + options.path)
const response = md5(HA1 + ':' + nonce + ':00000001:' + cnonce + ':' + qop + ':' + HA2)
options.headers.Authorization = 'Digest username="' + user + '",realm="' + realm + '",nonce="' + nonce + '",uri="' + options.path + '",cnonce="' + cnonce + '",nc=00000001,algorithm=MD5,response="' + response + '",qop="' + qop + '"'
return request(options, data)
} else {
return request(options, data)
}
})
}
const request = (options, data) => {
return new Promise(function(resolve, reject) {
const req = http.request(options, (res) => {
let resData
res.setEncoding('utf8')
res.on('data', (chunk) => {
resData += chunk
})
res.on('end', () => {
resolve(resData)
})
})
req.on('error', (e) => {
reject(e)
})
if (data) {
req.write(data)
}
req.end()
})
}
digestReqest js:
import { digestRequest } from './node-digest-auth-client'
// 默认的请求配置参数信息
const defaultOption = {
hostname: '127.0.0.1',
port: 80,
path: '/ISAPI/Security/userCheck',
method: 'GET',
headers: {
'Connection': 'Keep-Alive',
'Content-Type': 'application/json; charset="utf-8"'
}
}
export const ISApiRequest = function(option, data, username, password) {
// 将用户的请求参数信息与默认的请求参数信息进行合并
const reqOption = Object.assign(defaultOption, option)
return digestRequest(
reqOption,
data,
username,
password
)
}
业务代码:
// vue组件的method方法:
handleGetDeviceAbility: function() {
ISApiRequest(
{
hostname: '10.41.102.136',
port: 80,
path: '/ISAPI/System/capabilities',
method: 'GET'
},
null, 'admin', 'HKJFxmzcb@2018',
(err, data) => {
console.log(err)
console.log(data)
}
).then(resp => {
console.log('request response data is')
console.log(resp)
})
}
最终效果
优化思考:
1、 http库截止目前貌似已经不推荐使用了, 可以将http用needle进行调换,需要对部分接口做些调整;(已做优化, 优化文章地址见 https://blog.csdn.net/Harvay/article/details/112020107)
2、 响应数据目前有json以及xml格式,可以根据response header头信息进行判断后解析数据。
分享以上,供参考