Python - Zeep SOAP请求带头和时间戳

0 投票
1 回答
26 浏览
提问于 2025-04-13 17:57

我正在尝试使用 Zeep 从一个 wsdl 地址 获取响应。

这个过程需要提供用户名、密码、随机数(PasswordDigest)、时间戳和一些头信息。虽然在 SoapUI 中一切正常,但我在 Zeep 中却无法成功,服务器给我返回了一个错误:

错误:在验证消息时遇到安全错误

我猜问题出在头信息上,但我找不到正确的方法来添加它们。

这是在 SoapUI 中的样子

这是网络服务文档中提供的请求示例:

<soapenv:Envelope xmlns:soapenv=***schemas.xmlsoap.org/soap/envelope/
xmlns:mes="***economie.fgov.be/KBOpub/webservices/v1/messages"
xmlns:dat="***economie.fgov.be/KBOpub/webservices/v1/datamodel">
    <soapenv:Header>
        <wsse:Security>
            <wsu:Timestamp>
                <wsu:Created>2009-09-07T11:27:10.748Z</wsu:Created>
                <wsu:Expires>2009-09-07T11:32:10.748Z</wsu:Expires>
            </wsu:Timestamp>
            <wsse:UsernameToken>
                <wsse:Username>userid</wsse:Username>
                <wsse:Password>x3+DQlYgeVm3BAkobZivkDJ13zo=</wsse:Password>
                <wsse:Nonce>ENp2ha7j2Ar9cvWQeUybTQ==</wsse:Nonce>
                <wsu:Created>2009-09-07T11:27:10.716Z</wsu:Created>
            </wsse:UsernameToken>
       </wsse:Security>
       <mes:RequestContext>
           <mes:Id>c1576d0a-e762-40fe-abf9-ec3f2102650b</mes:Id>
           <mes:Language>fr</mes:Language>
       </mes:RequestContext>
    </soapenv:Header>
    <soapenv:Corps>
        <mes:ReadEnterpriseRequest>
            <dat:EnterpriseNumber>0314595348</dat:EnterpriseNumber>
        </mes:ReadEnterpriseRequest>
    </soapenv:Corps>
</soapenv:Envelope>

根据其他问题的回答,我尝试过的内容:

from zeep.wsse.username import UsernameToken
import datetime
from zeep.wsse.utils import WSU
from zeep.xsd import Element, ComplexType, String, Sequence

wsdl_url = '***bopub.economie.fgov.be/kbopubws110000/services/wsKBOPub?wsdl'

username = '***'
password = '******'

timestamp_token = WSU.Timestamp()
today_datetime = datetime.datetime.today()
expires_datetime = today_datetime + datetime.timedelta(minutes=5)
timestamp_elements = [
        WSU.Created(today_datetime.strftime("%Y-%m-%dT%H:%M:%S.%fZ")),
        WSU.Expires(expires_datetime.strftime("%Y-%m-%dT%H:%M:%S.%fZ"))]

timestamp_token.extend(timestamp_elements)
user_name_token = UsernameToken(username, password, timestamp_token=timestamp_token, use_digest=True)
client = Client(wsdl_url, wsse=user_name_token)
client.set_ns_prefix('mes', "***economie.fgov.be/kbopub/webservices/v1/messages")

headerQ = Element('{***schemas.xmlsoap.org/soap/envelope.xsd}Header', ComplexType([
  Element('{***economie.fgov.be/kbopub/webservices/v1/messages.xsd}RequestContext', ComplexType(
    Sequence([
      Element('{***economie.fgov.be/kbopub/webservices/v1/messages.xsd}Id', String()),
      Element('{***economie.fgov.be/kbopub/webservices/v1/messages.xsd}Language', String())
    ]))
  )
]))
header_value = headerQ({'Id': 'whatever', 'Language': 'fr'})
client.set_default_soapheaders([header_value])

client.service.ReadEnterprise(EnterpriseNumber='0314595348')

但我总是收到来自服务器的相同安全错误。

谢谢!

1 个回答

0

我找到了解决自己问题的方法,接下来我会解释我是怎么找到的,希望能帮到别人。

我首先查看了SoapUI中的http日志,看看请求应该是什么样子的:

<soapenv:Envelope xmlns:dat="http://economie.fgov.be/kbopub/webservices/v1/datamodel" xmlns:mes="http://economie.fgov.be/kbopub/webservices/v1/messages" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
   <soapenv:Header><wsse:Security 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"><wsu:Timestamp wsu:Id="TS-D7D646D96AD83C8414171093156745142"><wsu:Created>2024-03-20T10:46:07.451Z</wsu:Created><wsu:Expires>2024-03-20T10:51:07.451Z</wsu:Expires></wsu:Timestamp><wsse:UsernameToken wsu:Id="UsernameToken-D7D646D96AD83C8414171093156745141"><wsse:Username>***</wsse:Username><wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">***</wsse:Password><wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">DjBcs/i453gVH3io3+uUCw==</wsse:Nonce><wsu:Created>2024-03-20T10:46:07.451Z</wsu:Created></wsse:UsernameToken></wsse:Security>
      <mes:RequestContext>
         <mes:Id>whatever</mes:Id>
         <mes:Language>fr</mes:Language>
      </mes:RequestContext>
   </soapenv:Header>
   <soapenv:Body>
      <mes:ReadEnterpriseRequest>
         <dat:EnterpriseNumber>0422118165</dat:EnterpriseNumber>
      </mes:ReadEnterpriseRequest>
   </soapenv:Body>
</soapenv:Envelope>

然后我用HistoryPlugin对比了一下实际发送的内容:

from zeep.plugins import HistoryPlugin

history = HistoryPlugin()

[...]

client = Client(wsdl_url, wsse=user_name_token, plugins=[history])

from lxml import etree

for hist in [history.last_sent, history.last_received]:
    print(etree.tostring(hist["envelope"], encoding="unicode", pretty_print=True))

这样我就很容易看出需要修改的地方。最后我的代码变成了这样:

from zeep import Client
from zeep.wsse.username import UsernameToken
import datetime
from zeep.wsse.utils import WSU
from zeep.xsd import Element, ComplexType, String, Sequence
from zeep.plugins import HistoryPlugin

history = HistoryPlugin()

# WSDL URL
wsdl_url = 'https://kbopub.economie.fgov.be/kbopubws110000/services/wsKBOPub?wsdl'

# Username and password for authentication
username = ***
password = ***

timestamp_token = WSU.Timestamp()
today_datetime = datetime.datetime.now(datetime.timezone.utc)
expires_datetime = today_datetime + datetime.timedelta(minutes=5)
timestamp_elements = [
        WSU.Created((today_datetime.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3])+'Z'),
        WSU.Expires((expires_datetime.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3])+'Z')]

timestamp_token.extend(timestamp_elements)
user_name_token = UsernameToken(username, password, timestamp_token=timestamp_token, use_digest=True)
client = Client(wsdl_url, wsse=user_name_token, plugins=[history])
client.set_ns_prefix('ns1', "http://economie.fgov.be/kbopub/webservices/v1/datamodel")
client.set_ns_prefix('ns0', "http://economie.fgov.be/kbopub/webservices/v1/messages")
client.set_ns_prefix('soap-env', "http://schemas.xmlsoap.org/soap/envelope/")

# Creating a RequestContext SOAP header
headerQ = Element('{http://economie.fgov.be/kbopub/webservices/v1/messages}RequestContext',
                  ComplexType([Sequence([
                      Element('{http://economie.fgov.be/kbopub/webservices/v1/messages}Id', String()),
                      Element('{http://economie.fgov.be/kbopub/webservices/v1/messages}Language', String())
                  ])]))

# Setting the RequestContext SOAP header value
header_value = headerQ(Id='whatever', Language='fr')
client.set_default_soapheaders([header_value])

print(client.service.ReadEnterprise(EnterpriseNumber=i))

撰写回答