参考文章
https://blog.csdn.net/benkaoya/article/details/72477536
https://blog.csdn.net/zhizhengguan/article/details/109325688
一、简介
-
onvif规定,有些接口需要鉴权,有些接口不需要鉴权。例如onvif规定GetDeviceInformation接口时需要鉴权的。(下图为main函数流程分析,标红的api都需要实现鉴权)
-
市面上有些的IPC(网络摄像头),并没有严格的按照ONVIF规范执行,造成客户端不携带鉴权信息也能成功调用某些接口。
二、onvif哪些接口需要认证
onvif规定有些接口需要鉴权,有些接口不需要鉴权。那么该怎么知道哪些接口需要认证呢?
在官网的ONVIF-Core-Specification.pdf文档有详细的规定,文档的路径为https://www.onvif.org/specs/core/ONVIF-Core-Specification.pdf,在该文档的**[Access classes for service requests]**章节中有接口访问权限的相关规定,如下图所示:
「PRE_AUTH」的规定是:The service shall not require user authentication. Example: GetEndpointReference.(该服务不需要用户认证。例如:GetEndpointReference,即客户端调用该接口时,不需要携带用户名、密码认证信息)。
拿GetServices接口举个例子,在ONVIF-Core-Specification.pdf文档中找到GetServices接口定义(如下图所示),会有Access Class: PRE_AUTH的说明,表明客户端调用该接口时,不需要携带用户名、密码认证信息。
再看看GetDeviceInformation接口规定(如下图所示),Access Class: READ_SYSTEM,说明客户端调用该接口是需要进行认证。
三、相关数据结构和函数
生成gSOAP框架的代码,项目文件结构和解决编译报错可以参考佬的这篇文章:
https://blog.csdn.net/zhizhengguan/article/details/109325688
1.数据结构
定义在client\application\client.cpp:
鉴权信息包括用户名、密码
USERNAME
PASSWORD
static const char * USERNAME;
static const char * PASSWORD;
2.函数
soap_wsse_add_UsernameTokenDigest()
函数的声明在client\application\wsseapi.h
SOAP_FMAC1 int SOAP_FMAC2 soap_wsse_add_UsernameTokenDigest(struct soap *soap, const char *id, const char *username, const char *password);
函数的定义在client\application\wsseapi.cpp
/**
@fn int soap_wsse_add_UsernameTokenDigest(struct soap *soap, const char *id, const char *username, const char *password)
@brief Adds UsernameToken element for digest authentication.
@param soap context
@param[in] id string for signature referencing or NULL
@param[in] username string
@param[in] password string
@return SOAP_OK
Computes SHA1 digest of the time stamp, a nonce, and the password. The digest
provides the authentication credentials. Passwords are NOT sent in the clear.
@note
This release supports the use of at most one UsernameToken in the header.
*/
SOAP_FMAC1
int
SOAP_FMAC2
soap_wsse_add_UsernameTokenDigest(struct soap *soap, const char *id, const char *username, const char *password)
{
return soap_wsse_add_UsernameTokenDigest_at(soap, id, username, password, time(NULL));
}
/**
@fn int soap_wsse_add_UsernameTokenDigest_at(struct soap *soap, const char *id, const char *username, const char *password, time_t when)
@brief Adds UsernameToken element for digest authentication.
@param soap context
@param[in] id string for signature referencing or NULL
@param[in] username string
@param[in] password string
@param[in] when the time stamp to use for the digest hash
@return SOAP_OK
Computes SHA1 digest of the time stamp, a nonce, and the password. The digest
provides the authentication credentials. Passwords are NOT sent in the clear.
@note
This release supports the use of at most one UsernameToken in the header.
*/
SOAP_FMAC1
int
SOAP_FMAC2
soap_wsse_add_UsernameTokenDigest_at(struct soap *soap, const char *id, const char *username, const char *password, time_t when)
{
_wsse__Security *security = soap_wsse_add_Security(soap);
const char *created = soap_dateTime2s(soap, when);
char HA[SOAP_SMD_SHA1_SIZE], HABase64[29];
char nonce[SOAP_WSSE_NONCELEN], *nonceBase64;
DBGFUN2("soap_wsse_add_UsernameTokenDigest", "id=%s", id?id:"", "username=%s", username?username:"");
/* generate a nonce */
soap_wsse_rand_nonce(nonce, SOAP_WSSE_NONCELEN);
nonceBase64 = soap_s2base64(soap, (unsigned char*)nonce, NULL, SOAP_WSSE_NONCELEN);
/* The specs are not clear: compute digest over binary nonce or base64 nonce? */
/* compute SHA1(created, nonce, password) */
calc_digest(soap, created, nonce, SOAP_WSSE_NONCELEN, password, HA);
/* Hm...?
calc_digest(soap, created, nonceBase64, strlen(nonceBase64), password, HA);
*/
soap_s2base64(soap, (unsigned char*)HA, HABase64, SOAP_SMD_SHA1_SIZE);
/* populate the UsernameToken with digest */
soap_wsse_add_UsernameTokenText(soap, id, username, HABase64);
/* populate the remainder of the password, nonce, and created */
security->UsernameToken->Password->Type = (char*)wsse_PasswordDigestURI;
security->UsernameToken->Nonce = (struct wsse__EncodedString*)soap_malloc(soap, sizeof(struct wsse__EncodedString));
security->UsernameToken->Salt = NULL;
security->UsernameToken->Iteration = NULL;
if (!security->UsernameToken->Nonce)
return soap->error = SOAP_EOM;
soap_default_wsse__EncodedString(soap, security->UsernameToken->Nonce);
security->UsernameToken->Nonce->__item = nonceBase64;
security->UsernameToken->Nonce->EncodingType = (char*)wsse_Base64BinaryURI;
security->UsernameToken->wsu__Created = soap_strdup(soap, created);
return SOAP_OK;
}
鉴权信息包括用户名、密码,在HTTP传输过程中不能是明文,有一定的加密算法。
如果不清楚这个加密算法怎么回事,那么可以利用gSOAP源码中的soap_wsse_add_UsernameTokenDigest函数,可以轻松实现鉴权。要使用该函数,需要注意以下几点:
-
要使用soap_wsse_add_UsernameTokenDigest函数进行授权,在soapcpp2 生成ONVIF代码框架之前,要在onvif.h头文件开头加入(注意是import而不是include):
#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目录下,将这些文件拷贝到你的项目中,以便参与编译。
打开wsse.h,可以看到相应依赖
注意:依赖一定要自己去看,否则会出现一大堆未定义引用。 -
在加入上面说的.c和.h文件后,结果编译失败,提示找不到「openssl/evp.h」文件:
smdevp.h(54): fatal error C1083: 无法打开包括文件:“openssl/evp.h”: No such file or directory
究其原因,wsse系列函数依赖OpenSSL库,我们得去OpenSSL官网下载源代码来编译、安装,里面有我们需要的库文件和头文件。
四、鉴权功能实现
void setUSERNAME(const char *username)
{
USERNAME = username;
#if DEBUG
std::cout << "-------------------------setUSERNAME-------------------------" << "\n";
std::cout << "USERNAME = "<< USERNAME << "\n";
std::cout << "-------------------------------------------------------------" << "\n";
#endif
}
void setPASSWORD(const char *password)
{
PASSWORD = password;
#if DEBUG
std::cout << "-------------------------setPASSWORD-------------------------" << "\n";
std::cout << "PASSWORD = "<< password << "\n";
std::cout << "-------------------------------------------------------------" << "\n";
#endif
}
/************************************************************************
**函数: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(nullptr != username);
SOAP_ASSERT(nullptr != password);
result = soap_wsse_add_UsernameTokenDigest(soap, NULL, username, password);
SOAP_CHECK_ERROR(result, soap, "add_UsernameTokenDigest");
EXIT:
return result;
}