ONVIF協議網絡攝像機(IPC)客戶端程序開發(9):鑑權(認證)

版權聲明:本文爲博主原創文章,如要轉載請標明出處。 https://blog.csdn.net/benkaoya/article/details/72477536

1 專欄導讀

本專欄第一篇文章「專欄開篇」列出了專欄的完整目錄,按目錄順序閱讀,有助於你的理解,專欄前面文章講過的知識點(或代碼段),後面文章不會贅述。爲了節省篇幅,突出重點,在文章中展示的示例代碼僅僅是關鍵代碼,你可以在「專欄開篇」中獲取完整代碼。

如有錯誤,歡迎你的留言糾正!讓我們共同成長!你的「點贊」「打賞」是對我最大的支持和鼓勵!

2 前言

接着上一篇文章接續,上篇文章說到,在測試ONVIF標準的GetDeviceInformation接口時,有些IPC要求鑑權(認證),有些IPC不需要。其實總結起來應該是這樣:

  1. ONVIF規定,有些接口需要鑑權,有些接口不需要鑑權(在哪裏查詢等下會說明)。ONVIF規定GetDeviceInformation接口是需要鑑權的。

  2. 市面上的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」的就是都要認證的。


圖1

拿GetServices接口舉個例子,在ONVIF Core Specification文檔中找到GetServices接口定義(如下圖所示),會有Access Class: PRE_AUTH的說明,表明客戶端調用該接口時,不需要攜帶用戶名、密碼認證信息。


圖2

再看看GetDeviceInformation接口規定(如下圖所示),Access Class: READ_SYSTEM,說明客戶端調用該接口是需要進行認證。


圖3

4 如何認證

這裏的鑑權信息包括用戶名、密碼,在HTTP傳輸過程中不能是明文,有一定的加密算法。

如果你整不清楚這個加密算法怎麼回事,那麼,我推薦利用gSOAP源碼中的soap_wsse_add_UsernameTokenDigest函數,可以輕鬆實現鑑權。要使用該函數,需要注意以下幾點:

  1. 就像專欄前面文章提到過的一樣,要使用soap_wsse_add_UsernameTokenDigest函數進行授權,在soapcpp2 生成ONVIF代碼框架之前,要在onvif.h頭文件開頭加入:

    #import “wsse.h”

  2. 依賴gSOAP中的這些源碼:wsseapi.c、wsseapi.h、mecevp.c、mecevp.h、smdevp.c、smdevp.h、threads.c、threads.h、dom.c,這些文件在gsoap目錄和gsoap/plugin目錄下,將這些文件拷貝到你的項目中,以便參與編譯。

  3. 在加入上面說的.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對象,導致鑑權信息丟失,所示每次都要重新設置鑑權信息。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章