Zeep调用带Header和Timestamp的SOAP接口遇安全错误求助
问题描述
我尝试使用Zeep调用KBOpub的SOAP服务,接口要求提供用户名、密码、Nonce(PasswordDigest)、时间戳及请求头。在SoapUI中可正常调用,但用Zeep实现时,服务器返回错误:
Fault: A security error was encountered when verifying the message
我推测问题出在请求头的设置上,但无法找到正确的添加方式。
服务文档提供的请求示例如下:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:mes="http://economie.fgov.be/KBOpub/webservices/v1/messages" xmlns:dat="http://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:Body> <mes:ReadEnterpriseRequest> <dat:EnterpriseNumber>0314595348</dat:EnterpriseNumber> </mes:ReadEnterpriseRequest> </soapenv:Body> </soapenv:Envelope>
我参考其他方案尝试了以下代码,但仍收到相同的安全错误:
from zeep.wsse.username import UsernameToken import datetime from zeep.wsse.utils import WSU from zeep.xsd import Element, ComplexType, String, Sequence from zeep import Client wsdl_url = 'https://kbopub.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', "http://economie.fgov.be/kbopub/webservices/v1/messages") headerQ = Element('{http://schemas.xmlsoap.org/soap/envelope.xsd}Header', ComplexType([ Element('{http://economie.fgov.be/kbopub/webservices/v1/messages.xsd}RequestContext', ComplexType( Sequence([ Element('{http://economie.fgov.be/kbopub/webservices/v1/messages.xsd}Id', String()), Element('{http://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')
解决方案
你的问题集中在时间戳格式、RequestContext构造细节上,对照SoapUI的请求规范调整即可解决:
1. 修正时间戳的时区与格式
服务要求UTC时间,且需要保留三位毫秒精度,你之前用本地时间生成的格式不符合要求,改用utcnow()并调整格式化规则:
utc_now = datetime.datetime.utcnow() # 保留三位毫秒,结尾加Z表示UTC formatted_now = utc_now.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z" expires = utc_now + datetime.timedelta(minutes=5) formatted_expires = expires.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
2. 简化RequestContext的构造
无需手动定义复杂类型,直接用Zeep从WSDL中生成的类型创建请求头,避免命名空间错误:
# 替换原有的手动Header构造代码 request_context_type = client.get_type('mes:RequestContext') header_value = request_context_type(Id='c1576d0a-e762-40fe-abf9-ec3f2102650b', Language='fr') client.set_default_soapheaders([header_value])
3. 确保WS-Security参数的规范生成
Zeep的UsernameToken默认会处理Nonce和PasswordDigest的生成,但要明确指定时间戳参数,避免自动生成的时间与服务要求不符。
修正后的完整代码
from zeep.wsse.username import UsernameToken import datetime from zeep import Client wsdl_url = 'https://kbopub.economie.fgov.be/kbopubws110000/services/wsKBOPub?wsdl' username = '你的用户名' password = '你的密码' # 生成符合要求的UTC时间戳 utc_now = datetime.datetime.utcnow() formatted_now = utc_now.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z" expires = utc_now + datetime.timedelta(minutes=5) formatted_expires = expires.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z" # 初始化UsernameToken,自动处理Nonce、Digest和时间戳 user_token = UsernameToken( username, password, created=formatted_now, expires=formatted_expires, use_digest=True ) client = Client(wsdl_url, wsse=user_token) client.set_ns_prefix('mes', "http://economie.fgov.be/kbopub/webservices/v1/messages") # 用WSDL生成的类型创建RequestContext request_context = client.get_type('mes:RequestContext')( Id='c1576d0a-e762-40fe-abf9-ec3f2102650b', Language='fr' ) client.set_default_soapheaders([request_context]) # 调用接口 response = client.service.ReadEnterprise(EnterpriseNumber='0314595348') print(response)
额外排查建议
如果仍报错,抓包对比Zeep生成的请求和SoapUI的请求,重点检查:
<wsse:Security>节点的命名空间是否为http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd- Nonce、Created时间、PasswordDigest的格式是否与SoapUI生成的一致
- RequestContext是否和Security节点同级别,都在
<soapenv:Header>下
内容的提问来源于stack exchange,提问作者user23660943
相关产品推荐
相关产品推荐

