一、注册过程说明
这里使用一台海康的摄像头做实际测试。
GB28281注册过程有鉴权、不鉴权两种,本文实现的带鉴权的方式,基于GB281812016版。
本文原本想用C++库实现,但我这只有QT,配置基于第三方sip包的环境太不熟练,为节省时间暂且用nodejs先代用。
不带鉴权:
带鉴权:
1. 设备设置
2. 注册过程
1. 设备发送register注册消息
2. 服务器返回401 未登陆
3. 设备发送登陆认证
主要是要计算当中的response值,认证计算过程:
下面使用kd函数,表示对字符串使用 冒号 拼接后,计算md5,即:
如: kd(a,b)=md5(a+":"+b)kd(a,b) = md5(a + ":" + b)kd(a,b)=md5(a+":"+b)
HA1=kd(username,realm,passwd)HA2=kd(Method,Uri)HA1=kd(username,realm,passwd) HA2=kd(Method,Uri) HA1=kd(username,realm,passwd)HA2=kd(Method,Uri)
response有两种情况,一种带nonce,一种不带。
不带nonce:
response=kd(ha1,nonce,ha2)response = kd(ha1,nonce,ha2) response=kd(ha1,nonce,ha2)
带nonce
response=kd(ha1,nonce,nc,cnonce,qop,ha2)response = kd(ha1,nonce,nc,cnonce,qop,ha2) response=kd(ha1,nonce,nc,cnonce,qop,ha2)
4. 服务端校验正确,返回200ok
接下来服务端可以请求设备目录。
整体描述:
本文主要实现REGISTER的部分。
3. 签名校验
二、实现过程
依赖:
https://github.com/kirm/sip.js
1. 启动 sip
sip.start({logger: { send: function(message, address) { // logger.info("==send==:" , message,address); },recv: function(message, address) {// logger.info("==recv==:" , message,address); }}
},function(rq) {
});
返回值解析:
if(rq.method ==='REGISTER') { logger.info('call register');var username = sip.parseUri(rq.headers.to.uri).user;logger.info('register username',username);var userinfo = registry[username];//logger.info('userinfo', userinfo);if(!userinfo) {// 没有登记的用户,这里直接禁止授权logger.error('没有登记的用户,这里直接禁止授权:' , username);var session = {realm: realm};sip.send(digest.challenge(session, sip.makeResponse(rq, 401, 'Unauthorized')));return;}else {userinfo.session = userinfo.session || {realm: realm};if(!digest.authenticateRequest(userinfo.session, rq, {user: username, password: userinfo.password})) {sip.send(digest.challenge(userinfo.session, sip.makeResponse(rq, 401, 'Unauthorized')));}else {// 完成授权userinfo.contact = rq.headers.contact;var rs = sip.makeResponse(rq, 200, 'Ok');rs.headers.contact = rq.headers.contact;sip.send(rs);}}}
三、完整代码
var log4js = require('log4js');log4js.configure({appenders: {out: { type: 'stdout' },app: { type: 'file', filename: 'application.log' }},categories: {// getLogger 参数为空时,默认使用该分类default: { appenders: [ 'out', 'app' ], level: 'debug' }}
});var logger = log4js.getLogger();
const log4js_extend = require("log4js-extend");
log4js_extend(log4js, {path: __dirname + "/a.log",format: "at @name (@file:@line:@column)"
});var parseString = require('xml2js').parseString;
var sip = require('sip');
var digest = require('sip/digest');
var os = require('os');var server_account = '34020000002000000001';var registry = {'34020000001110000001': {password: '你的设备密码'}
};debugger;
var realm ='3402000000'; logger.info('localhost name='+realm);
sip.start({logger: { send: function(message, address) { // logger.info("==send==:" , message,address); },recv: function(message, address) {// logger.info("==recv==:" , message,address); }}
},
function(rq) {try {logger.info('----------------------',rq);if(rq.method ==='REGISTER') { logger.info('call register');var username = sip.parseUri(rq.headers.to.uri).user;logger.info('register username',username);var userinfo = registry[username];//logger.info('userinfo', userinfo);if(!userinfo) {// 没有登记的用户,这里直接禁止授权logger.error('没有登记的用户,这里直接禁止授权:' , username);var session = {realm: realm};sip.send(digest.challenge(session, sip.makeResponse(rq, 401, 'Unauthorized')));return;}else {// 这里应该对server_account再校验一下。但有的网上测试IPC程序server_account没用到uri里。这里先简单实现下原理。userinfo.session = userinfo.session || {realm: realm};if(!digest.authenticateRequest(userinfo.session, rq, {user: username, password: userinfo.password})) {sip.send(digest.challenge(userinfo.session, sip.makeResponse(rq, 401, 'Unauthorized')));}else {// 完成授权userinfo.contact = rq.headers.contact;var rs = sip.makeResponse(rq, 200, 'Ok');rs.headers.contact = rq.headers.contact;sip.send(rs);}}}} catch(e) {logger.error(e);sip.send(sip.makeResponse(rq, 500, "Server Internal Error"));}
});
正常运行后,可以看到海康摄像头处于在线状态。