UsernameToken

本文探讨了WS-Security中基于用户名密码的认证方法及其安全性改进措施。介绍了密码摘要、密钥派生及UsernameForCertificate等机制,以解决密码传输安全、重放攻击等问题。

使用用户名和密码来验证用户的身份是最普通也最常见的方法,虽然在安全性方面也比较弱,由于其运用的广泛性还是成为了WS-Security目前所支持的Security Token之一。其原理非常简单,用户在发送请求的时候,在Soap head中加入自己的用户名以及密码,接受请求的Service通过之前与Client建立的共享密码来验证密码的合法性从而实现鉴别用户的功能。 不过实际运用起来就不能考虑的那么简单了,该方法主要存在两个问题: 1. 在SOAP包中传输密码怎么保证密码的安全性? 2. 怎么从用户名密码中获得签名和加密所需要的密钥?

针对第一个问题有三种解决方案: 1. 使用运输层的安全协议(如SSL)来保证明文密码的安全性。 2. 对明文密码做摘要后再传送给Service。 3. 利用从密码派生出来的密钥来代替直接使用密码来实现身份鉴别。

第一种方法采用了WS-Security结合运输层的安全协议(SSL)来保证密码的安全,在本系列一开始的文章已经描述过运输层安全协议的缺点,所以在此不对该方法做详细介绍。

第二种方法类似于HTTP Digest Authentication,先来看一段使用该方法的示例:

<wsse:UsernameToken>

  <wsse:Username>NNK</wsse:Username>     

  <wsse:Password Type="...#PasswordDigest">          weYI3nXd8LjMNVksCKFV8t3rgHh3Rw==     </wsse:Password>

  <wsse:Nonce>WScqanjCEAC4mQoBE07sAQ==</wsse:Nonce>     

  <wsu:Created>2003-07-16T01:24:32Z</wsu:Created> 

</wsse:UsernameToken>
 从中看出这里使用了PasswordDigest类型的Password,从Password的内容也可以看出这里没有使用明文密码的形式。另外还多出了wsse:Nonce和wsu:Created两个元素。其中Password的内容的计算公式如下: Password_Digest = Base64 ( SHA-1 ( nonce + created + password ) ) 读者可能比较奇怪wsse:Nonce和wsu:Created这两个元素的作用。为什么不直接SHA-1(password) ? 这样做是为了避免重放(Replay)攻击。假设Alice以摘要的形式向Service发送了密码,如果Bob此时截获了Alice发送的密码摘要,然后再用它向Service发送请求,那么Service将误认为Bob也是合法用户。当我们加入Nonce和Created元素之后,Service可以检查收到的消息中的Nonce是否已经收到过了,或者在一段时间(5min)内是否收到了相同用户名密码,从而避免重放攻击的危险。不过使用PasswordDigest方式要求Service必须拥有密码的明文形式,也就是说Service可以看到每个用户的密码,这对用户来说增加了风险。因为通常情况下用户的密码是以hash的形式保存在Service端的,从而保证用户的信息不被泄漏。尽管通过PasswordDigest可以避免密码的明文传播,而且通过引入wsse:Nonce和wsu:Created可以避免重放攻击的危险,但是如果Bob能够把传送中的密码摘要完全的拦截下来(使它无法传送到Service),然后利用拦截下来的密码去冒充Alice去请求Service,那么Service将束手无策。为此,我们引入了第三种方法。

第三种方法和Kerberos协议中KDC向Client传送TGT的方式类似。我们可以看出前两种方式用户都将自己的密码发送给Service用于身份鉴别,难道为了证明自己的身份就必须把密钥(这里是密码)直接告诉别人吗?其实问题的关键在于Client能向Service证明它拥有只有C与S知道的密钥。而证明拥有的最直接方法就是告诉对方这个密钥,然后由Service比较这个密钥是否和它所知道的密钥一致,从而鉴别用户的身份。但是这种方法如前所述存在多种缺陷。既然仅仅需要Client证明它知道这个密钥,那么Client可以用这个密钥对一段消息做一个签名,然后将消息和签名同时发送给Service,Service用它所知道的Client的密钥也对同样的消息做一次签名,通过比较两个签名是否一致就可以确认Client是否真的拥有它的密钥。同样通过加密的方法Client也可以向Service证明自己是否真的拥有密钥(因为只有C与S密钥一致Service才能解密出用C密钥加密的消息)。这样一旦Client在消息中加入自己的一些特有信息(比如IP),即便Bob截获了消息但是由于他并不知道真正的密钥,看不到那些特有信息,也就无法冒充Alice。

通过这种间接证明拥有密钥的方法,我们同时解决了文章一开始提出的第二个问题:怎么从用户名密码中获得签名和加密所需要的密钥? 只要对密码做一些处理就可以从中派生出密钥。当然为了安全起见我们希望每次派生出来的密钥都不一样,这样就可以避免多次使用同一密钥而导致密钥被破解。下面就是WS-Security对密钥派生的元素定义:

<wsse:UsernameToken wsse:Id=”…”>     

  <wsse:Username>…</wsse:Username>

  <wsse11:Salt>…</wsse11:Salt>     

  <wsse11:Iteration>…</wsse11:Iteration> 

</wsse:UsernameToken> 

 

其中Salt是导致密钥变化的因子,Iteration是密钥派生时Hash的次数。密码的派生公式如下:

 K1 = SHA1( password + Salt) 

K2 = SHA1( K1 )   

 …

Kn = SHA1 ( Kn-1)

可以看到此时在UsernameToken已经不再包含Password元素,因为Client将通过使用从Password派生出密钥做签名做加密的方式来证明它拥有密钥,从而证明自己的身份。

由此看出第三种办法相对来说安全性大大提高了,但是在实际应用中以上介绍的三种的方法都不被推荐使用。第三种方法仍旧存在以下两个缺陷: 1. 直接使用密码派生密钥,同以往临时产生的会话密钥相比,密码一旦破解,所有由改密码派生的密钥也被破解。由于密码长期不变, 那么随后所有使用该密码加密的消息都没有安全性可言。而且该密码可能还被用于Client与其他Service的交互,那么被破解后带来的损失就大多了。 2. 用户密码必须以明文形式保存在Service端。

因此,在微软的WSE对安全的默认支持方式中采用了UsernameToken和Service端Certification的组合的方式来表示Security Token。下图就是WSE中已经实现的UsernameForCertificate对SOAP Envelop的扩展结构。              

从中可以看到SOAP Head中的wsse:UsernameToken已经被加密,被xenc:EncryptedData所替代,查看其Token Reference发现加密使用的Key来自xenc:EncryptedKey。如果你完整阅读了本系列的文章,你将不会对它太陌生,在XML Encryption中曾经对它的来由做了详细介绍。

      
Note由于使用对称密钥加密效率高,所以通常会使用对称密钥来加密数据,但是如何让消息的接受也获得对称密钥则成了一个问题。消息发送方不可能将对称密钥也随消息传递给消息接收方,此时利用非对称密钥来实现加密所用的对称密钥的传递成为了一个比较好的选择。EncrptedKey就是实现此种功能的扩展元素。

Client随机产生了一个对称密钥并用它来加密和签名SOAP Envelop中的其他元素(如UsernameToken),然后通过使用Service(消息接受方)的公钥(由于是公钥可以方便获取)来加密该对称密钥,以保证只有Service能够获得Client随机产生的对称密钥,从而达到验证消息完整性,解密数据以及鉴别用户身份的目的。以下是采用这种方式保证安全的SOAP Envelop的示例:

<?xml version="1.0" encoding="utf-8" ?> 

 <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2001/12/soap-envelope" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:xenc="http://www.w3.org/2001/04/xmlenc" xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility">

 <SOAP-ENV:Header>

 <wsse:Security xmlns:wsse="http://schemas.xmlsoap.org/ws/2002/secext">

 <wsse:UsernameToken>

 <wsse:Username>HotelService</wsse:Username> 

 <wsse:Password>myword</wsse:Password> 

 </wsse:UsernameToken>

 <xenc:EncryptedKey wsu:id="userSysmetricKey">

 <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5" /> 

 <ds:KeyInfo>

 <wsse:SecurityTokenReference>

 <wsse:KeyIdentifier ValueType="...oasis-wss-soap-message-security-1.1#ThumbPrintSHA1">LKiQ/CmFrJDJqCLFcjlhIsmZ/+0=</wsse:KeyIdentifier> 

 </wsse:SecurityTokenReference>

 </ds:KeyInfo>

 <xenc:CipherData>

 <xenc:CipherValue>G2wDCq24FsgBGerE...</xenc:CipherValue> 

 </xenc:CipherData>

 <xenc:ReferenceList>

 <xenc:DataReference URI="#DiscountResponse" /> 

 </xenc:ReferenceList>

 </xenc:EncryptedKey>

 <ds:Signature>

 <ds:SignedInfo>

 <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /> 

 <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#hmac-sha1" /> 

 <ds:Reference URI="#DiscountedBookingForPartnersResponse">

 <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#hmac-sha1" /> 

 <ds:DigestValue>JwFsd3eQc0iXlJm5PkLh7...</ds:DigestValue> 

 </ds:Reference>

 </ds:SignedInfo>

 <ds:SignatureValue>BSxlJbSiFdm5Plhk...</ds:SignatureValue> 

 <ds:KeyInfo>

 <wsse:SecurityTokenReference>

 <wsse:Reference URI="#userSysmetricKey" ValueType="...oasis-wss-soap-message-security-1.1#EncryptedKey" /> 

 </wsse:SecurityTokenReference>

 </ds:KeyInfo>

 </ds:Signature>

 </wsse:Security>

 </SOAP-ENV:Header>

 <SOAP-ENV:Body wsu:Id="DiscountedBookingForPartnersResponse">

 <s:GetSpecialDiscountedBookingForPartnersResponse xmlns:s="http://www.MyHotel.com/partnerservice">

 <xenc:EncryptedData wsu:Id="DiscountResponse" type="http://www.w3.org/2001/04/xmlenc#Element">

 <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc" /> 

 <CipherData>

 <CipherValue>XD6sFa0DrWsHdehrHdhcW0x...</CipherValue> 

 </CipherData>

 </xenc:EncryptedData>

 </s:GetSpecialDiscountedBookingForPartnersResponse>

 </SOAP-ENV:Body>

 </SOAP-ENV:Envelope>
       
Note以上介绍的方法UsernameForCertificate仅被WS-Security1.1支持。因为在1.1中才支持使用对称密钥签名。
如此,之前所提到的问题都可以解决了,让我们回顾一下: Q: 在SOAP包中传输密码怎么保证密码的安全性? A: 使用密钥加密,只有接受方能解密密码。

Q: 怎么从用户名密码中获得签名和加密所需要的密钥? A:  随机产生密钥,并通过接受方的公钥加密,保证密钥不被别人所知。

Q: 如何避免重放攻击? A: 由于其他人无法获得密钥,所以即便截获消息也无法冒充。

Q: 直接使用密码派生密钥被破解了怎么办? A: 密钥不再从密码派生,而是每次随机产生,即便被破解也不会影响其他的消息和其他的服务。

Q: 用户密码必须以明文形式保存在Service端? A: 由于密码被加密而不是做摘要所以不需要Service拥有明文密码。 应用场景: B2C网上购物,每个用户都有各自的用户名密码,而且可以方便的获得Server端的Certification。(比如Amazon)

参考资料:

OASIS Kerberos Token Profile 1.1 Protect Your Web Services Through The Extensible Policy Framework In WSE 3.0

系列文章

import socket # 目标服务器配置 SERVER_IP = "192.168.1.108" SERVER_PORT = 80 # 构造报文(注意保留原始格式中的转义字符) # http_request = ( # "POST /onvif/device_service HTTP/1.1\r\n" # "Host: 192.168.137.128:443\r\n" # "Content-Length: 961\r\n" # "\r\n" # '<?xml version="1.0" encoding="utf-8"?>' # '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">' # '<s:Header>' # '<Security xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">' # '<wsse:UsernameToken xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">' # '<wsse:Username>linzihao</wsse:Username>' # '<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">' # 'Qtj7NStEy0jJamBcv7Oi1oU08Yw=' # '</wsse:Password>' # '<wsse:Nonce>Bbly9xla5Dy19zbcdPeWmQ==</wsse:Nonce>' # '<wsu:Created xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">' # '2025-10-30T05:46:37Z' # '</wsu:Created>' # '</wsse:UsernameToken>' # '</Security>' # '</s:Header>' # '<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">' # '<GetDeviceInformation xmlns="http://www.onvif.org/ver10/device/wsdl"/>' # '</s:Body>' # '</s:Envelope>' # ) # 新报文内容(保持原始格式) # http_request = ( # "POST /onvif/device_service HTTP/1.1\r\n" # "Host: 192.168.137.128:2020\r\n" # "Content-Type: application/soap+xml; charset=utf-8\r\n" # "Content-Length: 299\r\n" # "\r\n" # '<?xml version="1.0" encoding="utf-8"?>' # '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">' # '<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">' # '<GetDeviceInformation xmlns="http://www.onvif.org/ver10/device/wsdl"/>' # '</s:Body>' # '</s:Envelope>' # ) # # 完整的请求报文(包含认证头) # http_request = ( # "POST /onvif/device_service HTTP/1.1\r\n" # "Host: 192.168.137.128:2020\r\n" # "Content-Type: application/soap+xml; charset=utf-8\r\n" # "Authorization: Digest username=\"linzihao\",realm=\"TP-Link IP-Camera\",qop=\"auth\",algorithm=\"MD5\",uri=\"/onvif/device_service\",nonce=\"TP-Link IP-Camera:5f2904695f2904a96b523a5a27b254da817bcd066a3da5c9\",nc=00000001,cnonce=\"5114C0217B52D2821F1C47269DE5453E\",opaque=\"64943214654649846565646421\",response=\"3e0f24492be6874cb69e1955868fbbab\"\r\n" # "Content-Length: 299\r\n" # "\r\n" # '<?xml version="1.0" encoding="utf-8"?><s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><GetDeviceInformation xmlns="http://www.onvif.org/ver10/device/wsdl"/></s:Body></s:Envelope>' # ) # http_request = ( # "POST /onvif/device_service HTTP/1.1\r\n" # "Host: 192.168.137.128:2020\r\n" # "Content-Type: application/soap+xml; charset=utf-8\r\n" # "Authorization: Digest username=\"linzihao\",realm=\"TP-Link IP-Camera\",qop=\"auth\",algorithm=\"MD5\",uri=\"/onvif/device_service\",nonce=\"TP-Link IP-Camera:06300469063004a9abd05593a8dd46cf654a949e09bcd19d\",nc=00000001,cnonce=\"E1E8051AAE6A37F8E1CC566BFE595431\",opaque=\"64943214654649846565646421\",response=\"d6bfcf48164f931d5cade139ca7db3b6\"\r\n" # "Content-Length: 961\r\n" # "\r\n" # '<?xml version="1.0" encoding="utf-8"?><s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Header><Security xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><wsse:UsernameToken xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><wsse:Username>linzihao</wsse:Username><wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">cBbNASgvn13AWzfBYG/McefZH1o=</wsse:Password><wsse:Nonce>VBuDixHPQa5VhxUtRapNzg==</wsse:Nonce><wsu:Created xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2025-10-31T03:46:58Z</wsu:Created></wsse:UsernameToken></Security></s:Header><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><GetDeviceInformation xmlns="http://www.onvif.org/ver10/device/wsdl"/></s:Body></s:Envelope>' # ) # http_request = ( # "POST /onvif/device_service HTTP/1.1\r\n" # "Host: 192.168.1.108:80\r\n" # "User-Agent: Happytime onvif client V12.7\r\n" # "Content-Type: application/soap+xml; charset=utf-8; action=\"http://www.onvif.org/ver10/device/wsdl/GetSystemDateAndTime\"\r\n" # "Content-Length: 1783\r\n" # "Connection: close\r\n" # "\r\n" # '<?xml version="1.0" encoding="UTF-8"?>' # '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" ' # 'xmlns:enc="http://www.w3.org/2003/05/soap-encoding" ' # 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ' # 'xmlns:xsd="http://www.w3.org/2001/XMLSchema" ' # 'xmlns:wsa="http://www.w3.org/2005/08/addressing" ' # 'xmlns:ds="http://www.w3.org/2000/09/xmldsig#" ' # 'xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" ' # 'xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" ' # 'xmlns:wsnt="http://docs.oasis-open.org/wsn/b-2" ' # 'xmlns:tt="http://www.onvif.org/ver10/schema" ' # 'xmlns:tds="http://www.onvif.org/ver10/device/wsdl" ' # 'xmlns:trt="http://www.onvif.org/ver10/media/wsdl" ' # 'xmlns:tr2="http://www.onvif.org/ver20/media/wsdl" ' # 'xmlns:tev="http://www.onvif.org/ver10/events/wsdl" ' # 'xmlns:tptz="http://www.onvif.org/ver20/ptz/wsdl" ' # 'xmlns:timg="http://www.onvif.org/ver20/imaging/wsdl" ' # 'xmlns:tan="http://www.onvif.org/ver20/analytics/wsdl" ' # 'xmlns:pt="http://www.onvif.org/ver10/pacs" ' # 'xmlns:tmd="http://www.onvif.org/ver10/deviceIO/wsdl" ' # 'xmlns:trp="http://www.onvif.org/ver10/replay/wsdl" ' # 'xmlns:tse="http://www.onvif.org/ver10/search/wsdl" ' # 'xmlns:trc="http://www.onvif.org/ver10/recording/wsdl" ' # 'xmlns:tac="http://www.onvif.org/ver10/accesscontrol/wsdl" ' # 'xmlns:tdc="http://www.onvif.org/ver10/doorcontrol/wsdl" ' # 'xmlns:tth="http://www.onvif.org/ver10/thermal/wsdl" ' # 'xmlns:tcr="http://www.onvif.org/ver10/credential/wsdl" ' # 'xmlns:tar="http://www.onvif.org/ver10/accessrules/wsdl" ' # 'xmlns:tsc="http://www.onvif.org/ver10/schedule/wsdl" ' # 'xmlns:trv="http://www.onvif.org/ver10/receiver/wsdl" ' # 'xmlns:tpv="http://www.onvif.org/ver10/provisioning/wsdl" ' # 'xmlns:ter="http://www.onvif.org/ver10/error" >' # '<s:Body>' # '<tds:GetSystemDateAndTime />' # '</s:Body>' # '</s:Envelope>' # ) # http_request = ( # "POST /onvif/device_service HTTP/1.1\r\n" # "Host: 192.168.1.108\r\n" # "Content-Type: application/soap+xml; charset=utf-8\r\n" # "Authorization: Digest username=\"admin\",realm=\"Login to 595f962a6e49d8c157d1dea6218657f2\",qop=\"auth\",algorithm=MD5,uri=\"/onvif/device_service\",nonce=\"b252aWYtZGlnZXN0OjY0NDI1MzAwNDAw\",nc=00000001,cnonce=\"AEE942C585CA9337AA6C932729B168B3\",opaque=\"\",response=\"7641183682761179bf6925bc454c8e0e\"\r\n" # "Content-Length: 299\r\n" # "\r\n" # '<?xml version="1.0" encoding="utf-8"?><s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><GetDeviceInformation xmlns="http://www.onvif.org/ver10/device/wsdl"/></s:Body></s:Envelope>' # ) http_request = ( "POST /onvif/device_service HTTP/1.1\r\n" "Host: 192.168.1.108\r\n" "Content-Type: application/soap+xml; charset=utf-8\r\n" 'Authorization: Digest username="admin",realm="Login to 595f962a6e49d8c157d1dea6218657f2",qop="auth",algorithm=MD5,uri="/onvif/device_service",nonce="b252aWYtZGlnZXN0OjY0NDM1NTA2Mjcw",nc=00000001,cnonce="AC6D5124A48ABF8A1A1EC1857CCE8DA9",opaque="",response="22c217025dd5b9f1f72502d639442203"\r\n' "Content-Length: 299\r\n" "\r\n" '<?xml version="1.0" encoding="utf-8"?><s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><GetDeviceInformation xmlns="http://www.onvif.org/ver10/device/wsdl"/></s:Body></s:Envelope>' ) try: # 创建TCP套接字 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.connect((SERVER_IP, SERVER_PORT)) s.sendall(http_request.encode('utf-8')) print(f"已发送 {len(http_request)} 字节到 {SERVER_IP}:{SERVER_PORT}") # 接收响应(可选) response = s.recv(4096) print(f"收到响应: {response.decode('utf-8')}") except Exception as e: print(f"发送失败: {str(e)}") ——参考该实现,写一个python脚本,向192.168.123.213的2020端口发送onvif请求并获取响应,采用digest+wss认证,digest算法使用MD5,第一个请求报文如下: http_request = ( “POST /onvif/device_service HTTP/1.1\r\n” “Host: 192.168.137.213:2020\r\n” “Content-Type: application/soap+xml; charset=utf-8\r\n” “Content-Length: 299\r\n” “\r\n” ‘’ ‘<s:Envelope xmlns:s=“http://www.w3.org/2003/05/soap-envelope”>’ ‘<s:Body xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance” xmlns:xsd=“http://www.w3.org/2001/XMLSchema”>’ ‘’ ‘</s:Body>’ ‘</s:Envelope>’ ) 该报文为digest认证的第一个请求,脚本(客户端)收到响应后需要计算response,生成digest认证头(http头部),但是由于我要实现digest+wss,所以脚本(客户端)发的第二个请求报文不仅要在http头部带digest认证信息,还需要在xml消息体包含ws-usernametoken,即采用以下报文消息体: <?xml version="1.0" encoding="utf-8"?><s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Header><Security xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><wsse:UsernameToken xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><wsse:Username>linzihao</wsse:Username><wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">BWJszKy0GcqTl7CFmKSy07QWILU=</wsse:Password><wsse:Nonce>sUzM+nV8sN66RzShZEyJhw==</wsse:Nonce><wsu:Created xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2025-11-07T06:18:56Z</wsu:Created></wsse:UsernameToken></Security></s:Header><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><GetDeviceInformation xmlns="http://www.onvif.org/ver10/device/wsdl"/></s:Body></s:Envelope> ——能不能实现该脚本
11-08
/****************************************************************************** \* Copyright (c) 2018-2018 TP-Link Systems Inc. * \* Filename: tan_passthrough.c \* Version: 1.0 \* Description: TAN请求中,与透传相关的指令处理接口 \* Author: Deng Peng<dengpeng@tp-link.com.cn> \* Date: 2019-03-12 ******************************************************************************/ \#include <stdio.h> \#include <stdlib.h> \#include <unistd.h> \#include "onvif_passthrough.h" \#include "soap_parse.h" \#include "soap_pack.h" \#include "soap_auth.h" \#include "soap_tan.h" \#include "md_active_cells.h" \#define ACTIVE_CELLS_FORMAT "Value=\"%s\" Name=\"ActiveCells\"" /********************************************************************************* \* tan:ModifyRules *********************************************************************************/ /****************************************************************************** \* 函数名称: soap_out_tan_modify_rules_rsp() \* 函数描述: 组装tan:ModifyRulesResponse element内容 \* 输 入: xml_buf -- 存放输出xml字串的内存地址 \* data -- 为空 \* 输 出: N/A \* 返 回 值: ERROR/OK ******************************************************************************/ LOCAL S32 soap_out_tan_modify_rules_rsp(ONVIF_BUF *xml_buf, void *data) { if (xml_buf == NULL) { ​ ONVIF_WARN("xml_buf == NULL."); ​ return ERROR; } /* 应答消息为空 */ SOAP_IF_FAIL_RET(soap_element(xml_buf, "tan:ModifyRulesResponse", NULL, NULL)); return OK; } /****************************************************************************** \* 函数名称: tan_modify_rules_json_to_xml() \* 函数描述: tan:ModifyRules请求的应答报文构造函数 \* 输 入: soap -- soap结构体 \* json_rsp -- 应答的json数据 \* 输 出: N/A \* 返 回 值: ERROR/OK ******************************************************************************/ LOCAL S32 tan_modify_rules_json_to_xml(SOAP_CONTEXT *soap, JSON_OBJ *json_rsp) { if (soap == NULL) { ​ ONVIF_TRACE("soap == NULL."); ​ return ERROR; } ONVIF_TRACE("tan:ModifyRules rsp."); return soap_generate_xml((p_out_fun)(soap_out_tan_modify_rules_rsp), soap, NULL); } /****************************************************************************** \* 函数名称: tan_modify_rules_xml_to_json() \* 函数描述: 解析tan:ModifyRules请求的具体内容 \* 输 入: soap -- soap结构体 \* 输 出: json_req -- 根据请求构造的json object \* 返 回 值: ERROR/OK ******************************************************************************/ LOCAL S32 tan_modify_rules_xml_to_json(SOAP_CONTEXT *soap, JSON_OBJ **json_req) { JSON_OBJ *jso_obj = NULL; S32 request_len = 0; S32 ch = 0; char *xml_start = NULL; char *xml_str = NULL; char **p = NULL; char charbuf[LEN_INFO] = {0}; char attr_in_name[2][10] = {{"Name"}, {"Value"}}; char attr_out_buf[2][(90 * 32)] = {{0}, {0}};/* 一个region_info的字符最大长度为90,region_info列表的成员个数不定,model_desc.xml中设置最大个数为32个 */ int name_str_len = sizeof(attr_in_name[0]); int value_str_len = sizeof(attr_out_buf[0]); int attr_num = sizeof(attr_in_name) / sizeof(attr_in_name[0]); if (soap == NULL || json_req == NULL) { ​ ONVIF_TRACE("soap == NULL."); ​ return ERROR; } ONVIF_TRACE("tan:ModifyRules parse."); /* 需要鉴权 */ if (OK != soap_usernametoken_auth(soap, UM_OPERATOR)) { ​ ONVIF_TRACE("Auth failed\n"); ​ soap_fault(soap, "SOAP-ENV:Sender", "ter:NotAuthorized", NULL, "Authority failure"); ​ soap->error = 400; ​ return ERROR; } /* 分析ModifyRules请求的具体内容 */ if (soap->request_begin != NULL && soap->request_end != NULL) { ​ request_len = soap->request_end - soap->request_begin; ​ if (request_len < 0) ​ { ​ ONVIF_TRACE("request_len < 0."); ​ goto error_parse; ​ } ​ xml_str = xml_start = soap->request_begin; ​ p = (char **)&xml_str; } else { ​ ONVIF_ERROR("soap request content is NULL."); ​ return ERROR; } while (((*p) - xml_start) < request_len) { ​ if ((ch = soap_get_tag(xml_start, request_len, p, charbuf, sizeof(charbuf))) == EOF) ​ { ​ goto error_parse; ​ } ​ if (charbuf[0] == '/') ​ { ​ continue; ​ } ​ if (TRUE == soap_match_tag(charbuf, "SimpleItem")) ​ { ​ if (IS_WHITE_SPACE(ch)) ​ { ​ ch = soap_parse_element_attr_out_len(xml_start, request_len, p, ​ attr_num, (char *)attr_in_name, name_str_len, (char *)attr_out_buf, value_str_len);/* parse attribute */ ​ ONVIF_TRACE("attr_out_buf_0[%s], attr_out_buf_1[%s]", attr_out_buf[0], attr_out_buf[1]); ​ if (ch == EOF) ​ { ​ ONVIF_TRACE("soap_parse_element_attr error."); ​ return ERROR; ​ } ​ if (0 == strcmp(attr_out_buf[0], "ActiveCells")) ​ { ​ ONVIF_TRACE("get value, break"); ​ break; ​ } ​ else ​ { ​ memset(attr_out_buf, 0, sizeof(attr_out_buf)); ​ } ​ } ​ continue; ​ } } /* 解析value值,生成json对象 */ jso_obj = parse_modify_rules_value(attr_out_buf[1]); if (NULL == jso_obj) { ​ ONVIF_TRACE("parse region_info error."); ​ return ERROR; } *json_req = jso_obj; return OK; error_parse: return ERROR; } /********************************************************************************* \* tan:ModifyAnalyticsModules *********************************************************************************/ /****************************************************************************** \* 函数名称: soap_out_tan_modify_analytics_modules_rsp() \* 函数描述: 组装tan:ModifyAnalyticsModulesResponse element内容 \* 输 入: xml_buf -- 存放输出xml字串的内存地址 \* data -- 为空 \* 输 出: N/A \* 返 回 值: ERROR/OK ******************************************************************************/ LOCAL S32 soap_out_tan_modify_analytics_modules_rsp(ONVIF_BUF *xml_buf, void *data) { /* tan:ModifyAnalyticsModulesResponse */ if (xml_buf == NULL) { ​ ONVIF_TRACE("xml_buf == NULL."); ​ return ERROR; } /* 应答消息为空 */ SOAP_IF_FAIL_RET(soap_element(xml_buf, "tan:ModifyAnalyticsModulesResponse", NULL, NULL)); return OK; } /****************************************************************************** \* 函数名称: tan_modify_analytics_modules_json_to_xml() \* 函数描述: tan:ModifyAnalyticsModules请求的应答报文构造函数 \* 输 入: soap -- soap结构体 \* json_rsp -- 应答的json数据 \* 输 出: N/A \* 返 回 值: ERROR/OK ******************************************************************************/ LOCAL S32 tan_modify_analytics_modules_json_to_xml(SOAP_CONTEXT *soap, JSON_OBJ *json_rsp) { if (soap == NULL) { ​ ONVIF_TRACE("soap == NULL."); ​ return ERROR; } ONVIF_TRACE("tan:ModifyAnalyticsModules rsp."); return soap_generate_xml((p_out_fun)(soap_out_tan_modify_analytics_modules_rsp), soap, NULL); } /****************************************************************************** \* 函数名称: tan_modify_analytics_modules_xml_to_json() \* 函数描述: 解析tan:ModifyAnalyticsModules请求的具体内容 \* 输 入: soap -- soap结构体 \* 输 出: json_req -- 根据请求构造的json object \* 返 回 值: ERROR/OK ******************************************************************************/ LOCAL S32 tan_modify_analytics_modules_xml_to_json(SOAP_CONTEXT *soap, JSON_OBJ **json_req) { JSON_OBJ *json_obj = NULL; JSON_OBJ *json_son_obj = NULL; JSON_OBJ *json_grandson_obj = NULL; JSON_OBJ *json_son_obj2 = NULL; JSON_OBJ *json_grandson_obj2 = NULL; S32 request_len = 0; S32 ch = 0; char *xml_start = NULL; char *xml_str = NULL; char **p = NULL; char charbuf[LEN_INFO] = {0}; char attr_name[1][5] = {{"Name"}}; int name_str_len = sizeof(attr_name[0]); char attr_value_buf[1][20] = {{0}}; int value_str_len = sizeof(attr_value_buf[0]); char attr_in_name[2][10] = {{"Name"}, {"Value"}}; char attr_out_buf[2][25] = {{0}, {0}}; if (soap == NULL || json_req == NULL) { ​ ONVIF_TRACE("soap == NULL."); ​ return ERROR; } ONVIF_TRACE("tan:ModifyAnalyticsModules parse."); /* 需要鉴权 */ if (OK != soap_usernametoken_auth(soap, UM_OPERATOR)) { ​ ONVIF_TRACE("Auth failed\n"); ​ soap_fault(soap, "SOAP-ENV:Sender", "ter:NotAuthorized", NULL, "Authority failure"); ​ soap->error = 400; ​ return ERROR; } /* 分析ModifyAnalyticsModules请求的具体内容 */ if (soap->request_begin != NULL && soap->request_end != NULL) { ​ request_len = soap->request_end - soap->request_begin; ​ if (request_len < 0) ​ { ​ ONVIF_TRACE("request_len < 0."); ​ return ERROR; ​ } ​ xml_str = xml_start = soap->request_begin; ​ p = (char **)&xml_str; } else { ​ ONVIF_ERROR("soap request content is NULL."); ​ return ERROR; } json_obj = jso_new_obj(); if (json_obj == NULL) { ​ goto error_out; } while (((*p) - xml_start) < request_len) { ​ if ((ch = soap_get_tag(xml_start, request_len, p, charbuf, sizeof(charbuf))) == EOF) ​ { ​ goto error_out; ​ } ​ if (charbuf[0] == '/') ​ { ​ continue; ​ } /* 首先判断 tan:AnalyticsModule的 Name是 MyCellMotionModule 还是 MyTamperDetecModule */ ​ if (TRUE == soap_match_tag(charbuf, "AnalyticsModule")) ​ { ​ if (IS_WHITE_SPACE(ch)) ​ { ​ memset(attr_value_buf, 0, sizeof(attr_value_buf)); ​ ch = soap_parse_element_attr_out_len(xml_start, request_len, p, ​ 1, (char *)attr_name, name_str_len, (char *)attr_value_buf, value_str_len);/* parse attribute */ ​ ONVIF_TRACE("attr_value_buf[%s]", attr_value_buf[0]); ​ if (ch == EOF) ​ { ​ ONVIF_TRACE("soap_parse_element_attr error."); ​ goto error_out; ​ } ​ if (0 == strcmp(attr_value_buf[0], "MyCellMotionModule")) ​ { ​ json_son_obj = jso_new_obj(); ​ if (json_son_obj == NULL) ​ { ​ goto error_out; ​ } ​ jso_obj_add(json_obj, "motion_detection", json_son_obj); ​ json_grandson_obj = jso_new_obj(); ​ if (json_grandson_obj == NULL) ​ { ​ goto error_out; ​ } ​ jso_obj_add(json_son_obj, "motion_det", json_grandson_obj); ​ ONVIF_TRACE("AnalyticsModule Name is MyCellMotionModule"); ​ continue; ​ } ​ else if (0 == strcmp(attr_value_buf[0], "MyTamperDetecModule")) ​ { ​ json_son_obj2 = jso_new_obj(); ​ if (json_son_obj2 == NULL) ​ { ​ goto error_out; ​ } ​ jso_obj_add(json_obj, "tamper_detection", json_son_obj2); ​ json_grandson_obj2 = jso_new_obj(); ​ if (json_grandson_obj2 == NULL) ​ { ​ goto error_out; ​ } ​ jso_obj_add(json_son_obj2, "tamper_det", json_grandson_obj2); ​ ONVIF_TRACE("AnalyticsModule Name is MyTamperDetecModule"); ​ continue; ​ } ​ } ​ continue; ​ } ​ if ((NULL == json_obj || NULL == json_son_obj || NULL == json_grandson_obj) && (NULL == json_obj || NULL == json_son_obj2 || NULL == json_grandson_obj2)) ​ { ​ continue; ​ } /* 然后读取两个Value ​ <tt:SimpleItem Name="Sensitivity" Value="high"/> ​ <tt:SimpleItem Name="Enabled" Value="on"/> */ ​ if (TRUE == soap_match_tag(charbuf, "SimpleItem") && 0 == strcmp(attr_value_buf[0], "MyCellMotionModule")) ​ { ​ if (IS_WHITE_SPACE(ch)) ​ { ​ memset(attr_out_buf, 0, sizeof(attr_out_buf)); ​ ch = soap_parse_element_attr_out_len(xml_start, request_len, p, ​ //attr_in_name, attr_out_buf, 2);/* parse attribute */ ​ 2, (char *)attr_in_name, (sizeof(attr_in_name)[0]), (char *)attr_out_buf, (sizeof(attr_out_buf)[0])); ​ ONVIF_TRACE("attr_out_buf_1[%s], attr_out_buf_1[%s]", attr_out_buf[0], attr_out_buf[1]); ​ if (ch == EOF) ​ { ​ ONVIF_TRACE("soap_parse_element_attr error."); ​ goto error_out; ​ } ​ if (TRUE == soap_match_tag(attr_out_buf[0], "Sensitivity")) ​ { ​ ONVIF_TRACE("set Sensitivity %s", attr_out_buf[1]); ​ if((0 == strcmp("low", attr_out_buf[1])) || ​ (0 == strcmp("medium", attr_out_buf[1])) || ​ (0 == strcmp("high", attr_out_buf[1]))) ​ { ​ jso_add_string(json_grandson_obj, "sensitivity", attr_out_buf[1]); ​ } \#ifdef SENSITIVITY_INT ​ /* 使用数字灵敏度 */ ​ else if (1 <= atoi(attr_out_buf[1]) && 100 >= atoi(attr_out_buf[1])) ​ { ​ jso_add_string(json_grandson_obj, "digital_sensitivity", attr_out_buf[1]); ​ jso_add_string(json_grandson_obj, "enabled", "on"); ​ } \#else ​ else if((1 <= atoi(attr_out_buf[1])) && (35 > atoi(attr_out_buf[1]))) ​ { ​ jso_add_string(json_grandson_obj, "sensitivity", "low"); ​ jso_add_string(json_grandson_obj, "enabled", "on"); ​ } ​ else if((35 <= atoi(attr_out_buf[1])) && (65 >= atoi(attr_out_buf[1]))) ​ { ​ jso_add_string(json_grandson_obj, "sensitivity", "medium"); ​ jso_add_string(json_grandson_obj, "enabled", "on"); ​ } ​ else if((65 < atoi(attr_out_buf[1])) && (100 >= atoi(attr_out_buf[1]))) ​ { ​ jso_add_string(json_grandson_obj, "sensitivity", "high"); ​ jso_add_string(json_grandson_obj, "enabled", "on"); ​ } \#endif ​ else if (0 == atoi(attr_out_buf[1])) ​ { ​ //jso_add_string(json_grandson_obj, "sensitivity", "medium"); ​ jso_add_string(json_grandson_obj, "enabled", "off"); ​ } ​ else ​ { ​ goto error_out; ​ } ​ } ​ else if (TRUE == soap_match_tag(attr_out_buf[0], "Enabled")) ​ { ​ jso_add_string(json_grandson_obj, "enabled", attr_out_buf[1]); ​ } ​ } ​ continue; ​ } ​ else if (TRUE == soap_match_tag(charbuf, "SimpleItem") && 0 == strcmp(attr_value_buf[0], "MyTamperDetecModule")) ​ { ​ if (IS_WHITE_SPACE(ch)) ​ { ​ memset(attr_out_buf, 0, sizeof(attr_out_buf)); ​ ch = soap_parse_element_attr_out_len(xml_start, request_len, p, ​ //attr_in_name, attr_out_buf, 2);/* parse attribute */ ​ 2, (char *)attr_in_name, (sizeof(attr_in_name)[0]), (char *)attr_out_buf, (sizeof(attr_out_buf)[0])); ​ ONVIF_TRACE("attr_out_buf_1[%s], attr_out_buf_1[%s]", attr_out_buf[0], attr_out_buf[1]); ​ if (ch == EOF) ​ { ​ ONVIF_TRACE("soap_parse_element_attr error."); ​ goto error_out; ​ } ​ if (TRUE == soap_match_tag(attr_out_buf[0], "Sensitivity")) ​ { ​ ONVIF_TRACE("set Sensitivity %s", attr_out_buf[1]); ​ if((0 == strcmp("low", attr_out_buf[1])) || ​ (0 == strcmp("medium", attr_out_buf[1])) || ​ (0 == strcmp("high", attr_out_buf[1]))) ​ { ​ jso_add_string(json_grandson_obj2, "sensitivity", attr_out_buf[1]); ​ } \#ifdef SENSITIVITY_INT ​ /* 使用数字灵敏度 */ ​ else if (1 <= atoi(attr_out_buf[1]) && 100 >= atoi(attr_out_buf[1])) ​ { ​ jso_add_string(json_grandson_obj2, "digital_sensitivity", attr_out_buf[1]); ​ jso_add_string(json_grandson_obj2, "enabled", "on"); ​ } \#else ​ else if((1 <= atoi(attr_out_buf[1])) && (35 > atoi(attr_out_buf[1]))) ​ { ​ jso_add_string(json_grandson_obj2, "sensitivity", "low"); ​ jso_add_string(json_grandson_obj2, "enabled", "on"); ​ } ​ else if((35 <= atoi(attr_out_buf[1])) && (65 >= atoi(attr_out_buf[1]))) ​ { ​ jso_add_string(json_grandson_obj2, "sensitivity", "medium"); ​ jso_add_string(json_grandson_obj2, "enabled", "on"); ​ } ​ else if((65 < atoi(attr_out_buf[1])) && (100 >= atoi(attr_out_buf[1]))) ​ { ​ jso_add_string(json_grandson_obj2, "sensitivity", "high"); ​ jso_add_string(json_grandson_obj2, "enabled", "on"); ​ } \#endif ​ else if (0 == atoi(attr_out_buf[1])) ​ { ​ //jso_add_string(json_grandson_obj, "sensitivity", "medium"); ​ jso_add_string(json_grandson_obj2, "enabled", "off"); ​ } ​ else ​ { ​ goto error_out; ​ } ​ } ​ else if (TRUE == soap_match_tag(attr_out_buf[0], "Enabled")) ​ { ​ jso_add_string(json_grandson_obj2, "enabled", attr_out_buf[1]); ​ } ​ } ​ continue; ​ } } /* 当json_son_obj和json_grandson_obj为空时,说明未解析出tan:AnalyticsModule的Name */ if ((NULL == json_obj || NULL == json_son_obj || NULL == json_grandson_obj) && (NULL == json_obj || NULL == json_son_obj2 || NULL == json_grandson_obj2)) { ​ ONVIF_TRACE("parse error, soap %s", soap->request_begin); ​ goto error_out; } /* 组织json object */ jso_add_string(json_obj, "method", "set"); ONVIF_TRACE("json: %s", json_object_to_json_string(json_obj)); *json_req = json_obj; return OK; error_out: jso_free_obj(json_obj); return ERROR; } /********************************************************************************* \* tan:GetRules *********************************************************************************/ /****************************************************************************** \* 函数名称: soap_out_tan_get_rules_rsp() \* 函数描述: 组装tan:GetRulesResponse element内容 \* 输 入: xml_buf -- 存放输出xml字串的内存地址 \* data -- 为空 \* 输 出: N/A \* 返 回 值: ERROR/OK ******************************************************************************/ LOCAL S32 soap_out_tan_get_rules_rsp(ONVIF_BUF *xml_buf, char *data) { char *buf = NULL; S32 len = 0; S32 ret = 0; S32 buf_len = 0; /* tan:GetRulesResponse */ if (xml_buf == NULL || data == NULL) { ​ ONVIF_TRACE("xml_buf == NULL."); ​ return ERROR; } /* tan:GetRulesResponse */ SOAP_IF_FAIL_RET(soap_element_begin_out(xml_buf, "tan:GetRulesResponse", NULL)); SOAP_IF_FAIL_RET(soap_element_begin_out(xml_buf, "tan:Rule", "Name=\"MyMotionDetectorRule\" Type=\"tt:CellMotionDetector\"")); SOAP_IF_FAIL_RET(soap_element_begin_out(xml_buf, "tt:Parameters", NULL)); ONVIF_TRACE("data: %s", data); buf_len = strlen(data) + sizeof(ACTIVE_CELLS_FORMAT); buf = SAFE_MALLOC(buf_len); if (buf == NULL) { ​ return ERROR; } memset(buf, 0, buf_len); len = snprintf(buf, buf_len, ACTIVE_CELLS_FORMAT, data); if (len > buf_len) { ​ ONVIF_TRACE("buf is too small"); ​ ret = ERROR; ​ goto out; } ONVIF_TRACE("buf: %s", (char *) buf); ret = soap_element(xml_buf, "tt:SimpleItem", NULL, buf); if (ret != OK) { ​ goto out; } ret = soap_element(xml_buf, "tt:SimpleItem", NULL, "Value=\"5\" Name=\"MinCount\""); if (ret != OK) { ​ goto out; } ret = soap_element(xml_buf, "tt:SimpleItem", NULL, "Value=\"1000\" Name=\"AlarmOnDelay\""); if (ret != OK) { ​ goto out; } ret = soap_element(xml_buf, "tt:SimpleItem", NULL, "Value=\"1000\" Name=\"AlarmOffDelay\""); if (ret != OK) { ​ goto out; } ret = soap_element_end_out(xml_buf, "tt:Parameters"); if (ret != OK) { ​ goto out; } ret = soap_element_end_out(xml_buf, "tan:Rule"); if (ret != OK) { ​ goto out; } ret = soap_element_end_out(xml_buf, "tan:GetRulesResponse"); out: SAFE_FREE(buf); return ret; } /****************************************************************************** \* 函数名称: tan_get_rules_json_to_xml() \* 函数描述: tan:GetRules请求的应答报文构造函数 \* 输 入: soap -- soap结构体 \* json_rsp -- 应答的json数据 \* 输 出: N/A \* 返 回 值: ERROR/OK ******************************************************************************/ LOCAL S32 tan_get_rules_json_to_xml(SOAP_CONTEXT *soap, JSON_OBJ *json_rsp) { int ret = 0; char *md_region_code = NULL; json_object* json_data = NULL; if (soap == NULL || json_rsp == NULL) { ​ ONVIF_TRACE("soap == NULL."); ​ return ERROR; } ONVIF_TRACE("tan:GetRules rsp."); /* 获取json_rsp中motion_detection/region_info的地址,不要单独释放json_data,否则会破坏json_rsp */ json_data = json_parse(json_rsp, "motion_detection", "region_info"); if (json_data == NULL) { ​ ONVIF_TRACE("json_parse module get NULL"); ​ return SOAP_FAULT; } ONVIF_TRACE("\n\n%s\n\n", json_object_to_json_string(json_data)); /* 读取移动侦测区域信息 */ md_region_code = md_get_active_cells_alloc(json_data); if (md_region_code == NULL) { ​ ONVIF_ERROR("get active cells failed"); ​ return SOAP_FAULT; } ret = soap_generate_xml((p_out_fun)(soap_out_tan_get_rules_rsp), soap, md_region_code); ONVIF_FREE(md_region_code); return ret; } /****************************************************************************** \* 函数名称: tan_get_rules_xml_to_json() \* 函数描述:解析onvif tan:GetRules请求报文的具体内容 \* 输 入: soap -- soap结构体 \* 输 出: N/A \* 返 回 值: ERROR/OK ******************************************************************************/ LOCAL S32 tan_get_rules_xml_to_json(SOAP_CONTEXT *soap, JSON_OBJ **json_req) { JSON_OBJ *json_obj = NULL; JSON_OBJ *json_sec = NULL; if (soap == NULL || json_req == NULL) { ​ ONVIF_TRACE("soap == NULL."); ​ return ERROR; } ONVIF_TRACE("tan:GetRules parse."); /* 需要鉴权 */ if (OK != soap_usernametoken_auth(soap, UM_OPERATOR)) { ​ ONVIF_TRACE("Auth failed\n"); ​ soap_fault(soap, "SOAP-ENV:Sender", "ter:NotAuthorized", NULL, "Authority failure"); ​ soap->error = 400; ​ return ERROR; } /* 不需要分析tan:GetRules请求的具体内容 */ /* 组织json object */ json_obj = jso_new_obj(); json_sec = jso_new_obj(); if (json_obj == NULL || json_sec == NULL) { ​ ONVIF_TRACE("jso_new_obj error."); ​ jso_free_obj(json_obj); ​ jso_free_obj(json_sec); ​ return ERROR; } jso_add_string(json_obj, "method", "get"); jso_obj_add(json_obj, "motion_detection", json_sec); jso_add_string(json_sec, "name", "motion_det"); jso_add_string(json_sec, "table", "region_info"); *json_req = json_obj; return OK; } /****************************************************************************** \* 函数名称: soap_out_tan_get_analytics_modules_rsp() \* 函数描述: 组装tan:GetAnalyticsModulesResponse element内容 \* 输 入: xml_buf -- 存放输出xml字串的内存地址 \* data -- 实际为NULL \* 输 出: N/A \* 返 回 值: ERROR/OK ******************************************************************************/ LOCAL S32 soap_out_tan_get_analytics_modules_rsp(ONVIF_BUF *xml_buf, void *data) { /* tan:GetRulesResponse */ if (NULL== xml_buf) { ​ ONVIF_TRACE("ptr is NULL."); ​ return ERROR; } MOTION_DETECT motion_detect; char simpleItem[60] = {0}; /* 从ds中读取移动侦测enable情况 */ memset(&motion_detect, 0, sizeof(MOTION_DETECT)); if (0 == ds_read(MOTION_DETECT_PATH, &motion_detect, sizeof(MOTION_DETECT))) { ​ ONVIF_TRACE("read motion_detect config fail.\n"); ​ return ERROR; } OD_ALARM od_alarm; char simple_item_od[60] = {0}; /* 从ds中读取遮挡侦测enable情况 */ memset(&od_alarm, 0, sizeof(OD_ALARM)); if (0 == ds_read(OD_ALARM_PATH, &od_alarm, sizeof(OD_ALARM))) { ​ ONVIF_TRACE("read od_alarm config fail.\n"); ​ return ERROR; } /* tan:GetRulesResponse */ SOAP_IF_FAIL_RET(soap_element_begin_out(xml_buf, "tan:GetAnalyticsModulesResponse", NULL)); SOAP_IF_FAIL_RET(soap_element_begin_out(xml_buf, "tan:AnalyticsModule", "Name=\"MyCellMotionModule\" Type=\"tt:CellMotionEngine\"")); SOAP_IF_FAIL_RET(soap_element_begin_out(xml_buf, "tt:Parameters", NULL)); /* IPC移动侦测关闭:0; IPC移动侦测打开:60 */ if (motion_detect.enabled) { ​ snprintf(simpleItem, sizeof(simpleItem), "Value=\"%d\" Name=\"Sensitivity\"", motion_detect.digital_sensitivity); ​ SOAP_IF_FAIL_RET(soap_element(xml_buf, "tt:SimpleItem", NULL, simpleItem)); } else { ​ SOAP_IF_FAIL_RET(soap_element(xml_buf, "tt:SimpleItem", NULL, "Value=\"0\" Name=\"Sensitivity\"")); } SOAP_IF_FAIL_RET(soap_element_begin_out(xml_buf, "tt:ElementItem", "Name=\"Layout\"")); SOAP_IF_FAIL_RET(soap_element_begin_out(xml_buf, "tt:CellLayout", "Rows=\"18\" Columns=\"22\"")); SOAP_IF_FAIL_RET(soap_element_begin_out(xml_buf, "tt:Transformation", NULL)); SOAP_IF_FAIL_RET(soap_element(xml_buf, "tt:Translate", NULL, "y=\"-1\" x=\"-1\"")); SOAP_IF_FAIL_RET(soap_element(xml_buf, "tt:Scale", NULL, "y=\"0.109999999\" x=\"0.0900000036\"")); SOAP_IF_FAIL_RET(soap_element_end_out(xml_buf, "tt:Transformation")); SOAP_IF_FAIL_RET(soap_element_end_out(xml_buf, "tt:CellLayout")); SOAP_IF_FAIL_RET(soap_element_end_out(xml_buf, "tt:ElementItem")); SOAP_IF_FAIL_RET(soap_element_end_out(xml_buf, "tt:Parameters")); SOAP_IF_FAIL_RET(soap_element_end_out(xml_buf, "tan:AnalyticsModule")); /*MyTamperDetecModule*/ SOAP_IF_FAIL_RET(soap_element_begin_out(xml_buf, "tan:AnalyticsModule", "Name=\"MyTamperDetecModule\" Type=\"tt:TamperEngine\"")); SOAP_IF_FAIL_RET(soap_element_begin_out(xml_buf, "tt:Parameters", NULL)); if (od_alarm.enabled) { ​ snprintf(simple_item_od, sizeof(simple_item_od), "Value=\"%d\" Name=\"Sensitivity\"", od_alarm.digital_sensitivity); ​ SOAP_IF_FAIL_RET(soap_element(xml_buf, "tt:SimpleItem", NULL, simple_item_od)); } else { ​ SOAP_IF_FAIL_RET(soap_element(xml_buf, "tt:SimpleItem", NULL, "Value=\"0\" Name=\"Sensitivity\"")); } SOAP_IF_FAIL_RET(soap_element_begin_out(xml_buf, "tt:ElementItem", "Name=\"Transformation\"")); SOAP_IF_FAIL_RET(soap_element_begin_out(xml_buf, "tt:Transformation", NULL)); SOAP_IF_FAIL_RET(soap_element(xml_buf, "tt:Translate", NULL, "y=\"-1\" x=\"-1\"")); SOAP_IF_FAIL_RET(soap_element(xml_buf, "tt:Scale", NULL, "y=\"0.109999999\" x=\"0.0900000036\"")); SOAP_IF_FAIL_RET(soap_element_end_out(xml_buf, "tt:Transformation")); SOAP_IF_FAIL_RET(soap_element_end_out(xml_buf, "tt:ElementItem")); SOAP_IF_FAIL_RET(soap_element_begin_out(xml_buf, "tt:ElementItem", "Name=\"Field\"")); SOAP_IF_FAIL_RET(soap_element_begin_out(xml_buf, "tt:PolygonConfiguration", NULL)); SOAP_IF_FAIL_RET(soap_element_begin_out(xml_buf, "tt:Polygon", NULL)); SOAP_IF_FAIL_RET(soap_element(xml_buf, "tt:Point", NULL, "y=\"0\" x=\"0\"")); SOAP_IF_FAIL_RET(soap_element(xml_buf, "tt:Point", NULL, "y=\"10000\" x=\"0\"")); SOAP_IF_FAIL_RET(soap_element(xml_buf, "tt:Point", NULL, "y=\"0\" x=\"10000\"")); SOAP_IF_FAIL_RET(soap_element(xml_buf, "tt:Point", NULL, "y=\"10000\" x=\"10000\"")); SOAP_IF_FAIL_RET(soap_element_end_out(xml_buf, "tt:Polygon")); SOAP_IF_FAIL_RET(soap_element_end_out(xml_buf, "tt:PolygonConfiguration")); SOAP_IF_FAIL_RET(soap_element_end_out(xml_buf, "tt:ElementItem")); SOAP_IF_FAIL_RET(soap_element_end_out(xml_buf, "tt:Parameters")); SOAP_IF_FAIL_RET(soap_element_end_out(xml_buf, "tan:AnalyticsModule")); SOAP_IF_FAIL_RET(soap_element_end_out(xml_buf, "tan:GetAnalyticsModulesResponse")); return OK; } /****************************************************************************** \* 函数名称: tan_get_analytics_modules_json_to_xml() \* 函数描述: tan:GetAnalyticsModules请求的应答报文构造函数 \* 输 入: soap -- soap结构体 \* json_rsp -- 应答的json数据 \* 输 出: N/A \* 返 回 值: ERROR/OK ******************************************************************************/ LOCAL S32 tan_get_analytics_modules_json_to_xml(SOAP_CONTEXT *soap, JSON_OBJ *json_rsp) { MOTION_DETECT motion_detect; if (soap == NULL) { ​ ONVIF_TRACE("soap == NULL."); ​ return ERROR; } ONVIF_TRACE("tan:GetAnalyticsModules rsp."); /* 从ds中读取移动侦测enable情况 */ memset(&motion_detect, 0, sizeof(MOTION_DETECT)); if (0 == ds_read(MOTION_DETECT_PATH, &motion_detect, sizeof(MOTION_DETECT))) { ​ ONVIF_TRACE("read motion_detect config fail.\n"); ​ soap->error = SOAP_FAULT; ​ return ERROR; } return soap_generate_xml((p_out_fun)(soap_out_tan_get_analytics_modules_rsp), soap, NULL); } /****************************************************************************** \* 函数名称: tan_get_analytics_modules_xml_to_json() \* 函数描述: tan:GetAnalyticsModules请求的处理函数 \* 输 入: soap -- soap结构体 ​ json_rsp-- 解析xml后得到的json数据 \* 输 出: N/A \* 返 回 值: ERROR/OK ******************************************************************************/ LOCAL S32 tan_get_analytics_modules_xml_to_json(SOAP_CONTEXT *soap, JSON_OBJ **json_req) { JSON_OBJ *json_obj = NULL; JSON_OBJ *json_sec = NULL; if (soap == NULL || json_req == NULL) { ​ ONVIF_TRACE("soap == NULL."); ​ return ERROR; } ONVIF_TRACE("tan:GetAnalyticsModules parse."); /* 需要鉴权 */ if (OK != soap_usernametoken_auth(soap, UM_OPERATOR)) { ​ ONVIF_TRACE("Auth failed\n"); ​ soap_fault(soap, "SOAP-ENV:Sender", "ter:NotAuthorized", NULL, "Authority failure"); ​ soap->error = 400; ​ return ERROR; } /* 不需要分析SetSystemFactoryDefault请求的具体内容 */ /* 组织json object */ json_obj = jso_new_obj(); json_sec = jso_new_obj(); if (json_obj == NULL || json_sec == NULL) { ​ ONVIF_WARN("jso_new_obj error."); ​ jso_free_obj(json_obj); ​ jso_free_obj(json_sec); ​ return ERROR; } jso_add_string(json_obj, "method", "get"); jso_obj_add(json_obj, "motion_detection", json_sec); jso_add_string(json_sec, "name", "motion_det"); jso_add_string(json_sec, "table", "region_info"); *json_req = json_obj; return OK; } /********************************************************************************* \* Soap tan Handles init *********************************************************************************/ void tan_passthrough_init() { onvif_passthrough_handle_add("tan:ModifyRules", ​ tan_modify_rules_xml_to_json, ​ tan_modify_rules_json_to_xml); onvif_passthrough_handle_add("tan:ModifyAnalyticsModules", ​ tan_modify_analytics_modules_xml_to_json, ​ tan_modify_analytics_modules_json_to_xml); onvif_passthrough_handle_add("tan:GetRules", ​ tan_get_rules_xml_to_json, ​ tan_get_rules_json_to_xml); onvif_passthrough_handle_add("tan:GetAnalyticsModules", ​ tan_get_analytics_modules_xml_to_json, ​ tan_get_analytics_modules_json_to_xml); } ——这个是关于透传层怎么调用soap_usernametoken_auth的例子,以下是目前的报文:只有digest凭据且验证通过情况下返回了200 OK但是SOAP消息体不对,发挥了SOAP FAULT,请问怎么解决? HTTP/1.1 200 OK Connection: close Content-Type: application/soap+xml; charset=utf-8 Content-Length: 1935 <?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV=“http://www.w3.org/2003/05/soap-envelope” xmlns:SOAP-ENC=“http://www.w3.org/2003/05/soap-encoding” xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance” xmlns:xsd=“http://www.w3.org/2001/XMLSchema” xmlns:wsa=“http://schemas.xmlsoap.org/ws/2004/08/addressing” xmlns:wsdd=“http://schemas.xmlsoap.org/ws/2005/04/discovery” xmlns:chan=“http://schemas.microsoft.com/ws/2005/02/duplex” xmlns:wsse=“http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd” xmlns:wsu=“http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd” xmlns:wsa5=“http://www.w3.org/2005/08/addressing” xmlns:xmime=“http://tempuri.org/xmime.xsd” xmlns:xop=“http://www.w3.org/2004/08/xop/include” xmlns:wsrfbf=“http://docs.oasis-open.org/wsrf/bf-2” xmlns:wstop=“http://docs.oasis-open.org/wsn/t-1” xmlns:wsrfr=“http://docs.oasis-open.org/wsrf/r-2” xmlns:wsnt=“http://docs.oasis-open.org/wsn/b-2” xmlns:tt=“http://www.onvif.org/ver10/schema” xmlns:ter=“http://www.onvif.org/ver10/error” xmlns:tns1=“http://www.onvif.org/ver10/topics” xmlns:tds=“http://www.onvif.org/ver10/device/wsdl” xmlns:tmd=“http://www.onvif.org/ver10/deviceIO/wsdl” xmlns:trt=“http://www.onvif.org/ver10/media/wsdl” xmlns:tev=“http://www.onvif.org/ver10/events/wsdl” xmlns:tdn=“http://www.onvif.org/ver10/network/wsdl” xmlns:timg=“http://www.onvif.org/ver20/imaging/wsdl” xmlns:trp=“http://www.onvif.org/ver10/replay/wsdl” xmlns:tan=“http://www.onvif.org/ver20/analytics/wsdl” xmlns:tptz=“http://www.onvif.org/ver20/ptz/wsdl”> SOAP-ENV:Body SOAP-ENV:Fault SOAP-ENV:Code SOAP-ENV:ValueSOAP-ENV:Sender</SOAP-ENV:Value> SOAP-ENV:Subcode SOAP-ENV:Valueter:NotAuthorized</SOAP-ENV:Value> </SOAP-ENV:Subcode> </SOAP-ENV:Code> SOAP-ENV:Reason <SOAP-ENV:Text xml:lang=“en”>Authority failure</SOAP-ENV:Text> </SOAP-ENV:Reason> </SOAP-ENV:Fault> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
10-07
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值