1 專欄導讀
本專欄第一篇文章「專欄開篇」列出了專欄的完整目錄,按目錄順序閱讀,有助於你的理解,專欄前面文章講過的知識點(或代碼段),後面文章不會贅述。爲了節省篇幅,突出重點,在文章中展示的示例代碼僅僅是關鍵代碼,你可以在「專欄開篇」中獲取完整代碼。
如有錯誤,歡迎你的留言糾正!讓我們共同成長!你的「點贊」或「打賞」是對我最大的支持和鼓勵!
2 前言
接着上一篇文章接續,上篇文章說到,在測試ONVIF標準的GetDeviceInformation接口時,有些IPC要求鑑權(認證),有些IPC不需要。其實總結起來應該是這樣:
ONVIF規定,有些接口需要鑑權,有些接口不需要鑑權(在哪裏查詢等下會說明)。ONVIF規定GetDeviceInformation接口是需要鑑權的。
市面上的IPC攝像頭,特別是山寨貨,並沒有嚴格按ONVIF規範執行,造成對於有的IPC,客戶端不攜帶鑑權信息也能成功調用GetDeviceInformation接口。
3 ONVIF哪些接口需要認證
辣麼,問題來了,哪些ONVIF接口需要鑑權,哪些不需要鑑權,在哪裏可以查看呢?答案是,在官網的ONVIF Core Specification文檔中有詳細的規定,如Version 16.12的版本爲《ONVIF-Core-Specification-v1612.pdf》。在該文檔的「Access classes for service requests」章節中有接口訪問權限的相關規定,如下圖所示。比如「PRE_AUTH」的規定是:The service shall not require user authentication,那非「PRE_AUTH」的就是都要認證的。
拿GetServices接口舉個例子,在ONVIF Core Specification文檔中找到GetServices接口定義(如下圖所示),會有Access Class: PRE_AUTH的說明,表明客戶端調用該接口時,不需要攜帶用戶名、密碼認證信息。
再看看GetDeviceInformation接口規定(如下圖所示),Access Class: READ_SYSTEM,說明客戶端調用該接口是需要進行認證。
4 如何認證
這裏的鑑權信息包括用戶名、密碼,在HTTP傳輸過程中不能是明文,有一定的加密算法。
如果你整不清楚這個加密算法怎麼回事,那麼,我推薦利用gSOAP源碼中的soap_wsse_add_UsernameTokenDigest函數,可以輕鬆實現鑑權。要使用該函數,需要注意以下幾點:
就像專欄前面文章提到過的一樣,要使用soap_wsse_add_UsernameTokenDigest函數進行授權,在soapcpp2 生成ONVIF代碼框架之前,要在onvif.h頭文件開頭加入:
#import “wsse.h”
依賴gSOAP中的這些源碼:wsseapi.c、wsseapi.h、mecevp.c、mecevp.h、smdevp.c、smdevp.h、threads.c、threads.h、dom.c,這些文件在gsoap目錄和gsoap/plugin目錄下,將這些文件拷貝到你的項目中,以便參與編譯。
在加入上面說的.c和.h文件後,結果編譯失敗,提示找不到「openssl/evp.h」文件:
smdevp.h(54): fatal error C1083: 無法打開包括文件:“openssl/evp.h”: No such file or directory
究其原因,wsse系列函數依賴OpenSSL庫,我們得去OpenSSL官網下載源代碼來編譯、安裝,裏面有我們需要的庫文件和頭文件。
5 安裝OpenSSL
如果你是跟着我專欄在學習,在專欄前面文章「使用gSOAP生成ONVIF框架代碼」中,已經安裝過OpenSSL了,對於還沒安裝OpenSSL的,可以參考那篇文章如何安裝。
6 實現認證
代碼如下,重點在ONVIF_SetAuthInfo函數(是對soap_wsse_add_UsernameTokenDigest的二次封裝),相比於上一篇文章,這次的ONVIF_GetDeviceInformation函數內部,增加了設置鑑權信息,在調用soap_call___tds__GetDeviceInformation之前,先調用ONVIF_SetAuthInfo函數設置鑑權信息。
你可以拿一個需要鑑權的IPC來測試,通過開啓、關閉ONVIF_SetAuthInfo語句,來觀察效果。注意:你要是拿一個本就不需要鑑權的IPC,是測不出預期效果的。
#define USERNAME "admin"
#define PASSWORD "admin"
/************************************************************************
**函數:ONVIF_SetAuthInfo
**功能:設置認證信息
**參數:
[in] soap - soap環境變量
[in] username - 用戶名
[in] password - 密碼
**返回:
0表明成功,非0表明失敗
**備註:
************************************************************************/
static int ONVIF_SetAuthInfo(struct soap *soap, const char *username, const char *password)
{
int result = 0;
SOAP_ASSERT(NULL != username);
SOAP_ASSERT(NULL != password);
result = soap_wsse_add_UsernameTokenDigest(soap, NULL, username, password);
SOAP_CHECK_ERROR(result, soap, "add_UsernameTokenDigest");
EXIT:
return result;
}
/************************************************************************
**函數:ONVIF_GetDeviceInformation
**功能:獲取設備基本信息
**參數:
[in] DeviceXAddr - 設備服務地址
**返回:
0表明成功,非0表明失敗
**備註:
************************************************************************/
int ONVIF_GetDeviceInformation(const char *DeviceXAddr)
{
int result = 0;
struct soap *soap = NULL;
struct _tds__GetDeviceInformation devinfo_req;
struct _tds__GetDeviceInformationResponse devinfo_resp;
SOAP_ASSERT(NULL != DeviceXAddr);
SOAP_ASSERT(NULL != (soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT)));
ONVIF_SetAuthInfo(soap, USERNAME, PASSWORD);
memset(&devinfo_req, 0x00, sizeof(devinfo_req));
memset(&devinfo_resp, 0x00, sizeof(devinfo_resp));
result = soap_call___tds__GetDeviceInformation(soap, DeviceXAddr, NULL, &devinfo_req, &devinfo_resp);
SOAP_CHECK_ERROR(result, soap, "GetDeviceInformation");
dump_tds__GetDeviceInformationResponse(&devinfo_resp);
EXIT:
if (NULL != soap) {
ONVIF_soap_delete(soap);
}
return result;
}
用大華IPC進行測試,執行結果如下:
================= + dump_tds__GetDeviceInformationResponse + >>>
Manufacturer: Dahua
Model: IPC-HDW1025C
Serial Number: 2K04788PAA00441
Hardware Id: 1.00
Firmware Version: 2.420.Dahua 00.14.R, build: 2016-06-18
================= - dump_tds__GetDeviceInformationResponse - <<<
7 特別注意
需要特別、特別、特別注意的是:但凡是ONVIF規定要鑑權的接口,每次調用之前,都要重新設置一次鑑權信息(即調用ONVIF_SetAuthInfo函數),哪怕你之前已經設置過鑑權信息了,否則後續調用ONVIF接口依然會報錯。
我們來做個測試,連續兩次調用soap_call___tds__GetDeviceInformation接口,第一次調用之前有設置鑑權信息,第二次調用之前沒有設置鑑權信息,代碼如下:
int ONVIF_GetDeviceInformation2(const char *DeviceXAddr)
{
int result = 0;
struct soap *soap = NULL;
struct _tds__GetDeviceInformation devinfo_req;
struct _tds__GetDeviceInformationResponse devinfo_resp;
SOAP_ASSERT(NULL != DeviceXAddr);
SOAP_ASSERT(NULL != (soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT)));
/* 第一次調用soap_call___tds__GetDeviceInformation之前有設置鑑權信息 */
ONVIF_SetAuthInfo(soap, USERNAME, PASSWORD);
memset(&devinfo_req, 0x00, sizeof(devinfo_req));
memset(&devinfo_resp, 0x00, sizeof(devinfo_resp));
result = soap_call___tds__GetDeviceInformation(soap, DeviceXAddr, NULL, &devinfo_req, &devinfo_resp);
SOAP_CHECK_ERROR(result, soap, "GetDeviceInformation");
dump_tds__GetDeviceInformationResponse(&devinfo_resp);
/* 第二次調用soap_call___tds__GetDeviceInformation之前沒有設置鑑權信息,導致調用失敗 */
memset(&devinfo_req, 0x00, sizeof(devinfo_req));
memset(&devinfo_resp, 0x00, sizeof(devinfo_resp));
result = soap_call___tds__GetDeviceInformation(soap, DeviceXAddr, NULL, &devinfo_req, &devinfo_resp);
SOAP_CHECK_ERROR(result, soap, "GetDeviceInformation");
dump_tds__GetDeviceInformationResponse(&devinfo_resp);
EXIT:
if (NULL != soap) {
ONVIF_soap_delete(soap);
}
return result;
}
同樣用大華IPC進行測試,執行結果如下,第二次調用soap_call___tds__GetDeviceInformation失敗:
================= + dump_tds__GetDeviceInformationResponse + >>>
Manufacturer: Dahua
Model: IPC-HDW1025C
Serial Number: 2K04788PAA00441
Hardware Id: 1.00
Firmware Version: 2.420.Dahua 00.14.R, build: 2016-06-18
================= - dump_tds__GetDeviceInformationResponse - <<<
[soap] GetDeviceInformation error: 401, is internal, HTTP Error
之所以會如此,究其根源,是因爲IPC的應答信息會重置soap對象,導致鑑權信息丟失,所示每次都要重新設置鑑權信息。