如何通过Windows证书库证书认证Zeep SOAP客户端?
问题详情
将.pfx证书转换为Zeep要求的.pem格式后,通过BinarySignature传递证书和密钥给Client对象,调用接口时触发错误:
zeep.exceptions.SignatureVerificationFailed
跟踪到Zeep的signature.py中_verify_envelope_with_key函数,发现是代码找不到SOAP Header导致抛出异常:
header = envelope.find(QName(soap_env, "Header")) if header is None: raise SignatureVerificationFailed()
已知目标WSDL端点包含<sp:HttpsToken RequireClientCertificate="false"/>,尝试关闭验证但无效,怀疑是否需要直接从Windows证书库加载.pfx,避免转换时丢失CA相关数据。
当前使用的核心代码:
def setup_client(): session = Session() session.verify = False session.cert = (CERT_PATH, KEY_PATH) transport = Transport(session=session) settings = Settings(strict=False, xml_huge_tree=True) wsse = BinarySignature(certfile=CERT_PATH, key_file=KEY_PATH) client = Client(WSDL_URL, transport=transport, settings=settings, wsse=wsse) return client def main(): load_dotenv() client = setup_client() response = client.service.Logout(sessionId=0)
补充:能正常拉取WSDL信息,但无法发送业务请求。
解决方案
1. 检查.pfx转.pem的完整性
转换时必须确保导出完整的证书链(包括根CA和中间证书),否则签名验证会缺少信任依据。使用openssl转换的正确命令:
# 导出带完整链的证书 openssl pkcs12 -in your_cert.pfx -out cert.pem -nokeys # 导出未加密的私钥 openssl pkcs12 -in your_cert.pfx -out key.pem -nocerts -nodes
打开生成的cert.pem,确认里面有多段-----BEGIN CERTIFICATE-----/-----END CERTIFICATE-----块(对应完整证书链)。
2. 移除WSSE签名配置(如果服务端不需要)
WSDL中的<sp:HttpsToken RequireClientCertificate="false"/>说明服务端可能仅要求HTTPS层面的客户端证书验证,不需要WSSE签名。此时添加BinarySignature反而会让Zeep强制验证响应签名,但服务端可能未返回带签名的SOAP Header,导致报错。修改后的代码:
def setup_client(): session = Session() session.verify = False # 生产环境建议指定可信CA证书,不要直接关闭 session.cert = (CERT_PATH, KEY_PATH) transport = Transport(session=session) settings = Settings(strict=False, xml_huge_tree=True) # 去掉wsse参数,仅保留HTTPS客户端证书配置 client = Client(WSDL_URL, transport=transport, settings=settings) return client
3. 直接从Windows证书库加载.pfx
如果转换.pem确实丢失了数据,可以用pywin32和cryptography直接读取Windows证书存储中的证书,避免转换环节:
from cryptography.hazmat.primitives import serialization from cryptography.hazmat.backends import default_backend import win32cryptography def load_cert_from_windows(thumbprint): # 从当前用户个人证书存储加载 store = win32cryptography.CertOpenSystemStore(None, "MY") cert_ctx = win32cryptography.CertFindCertificateInStore( store, win32cryptography.X509_ASN_ENCODING | win32cryptography.PKCS_7_ASN_ENCODING, 0, win32cryptography.CERT_FIND_SHA1_HASH, bytes.fromhex(thumbprint.replace(":", "")), None ) # 导出带私钥的PFX数据 pfx_data = win32cryptography.PFXExportCertStoreEx( store, "", "", None, win32cryptography.EXPORT_PRIVATE_KEYS | win32cryptography.PFX_EXPORT_INCLUDE_ROOT ) # 解析PFX pfx = serialization.pkcs12.load_pkcs12( pfx_data, password=None, # 证书有密码则传入对应字符串 backend=default_backend() ) # 转为PEM格式字符串 cert_pem = pfx.cert.public_bytes(encoding=serialization.Encoding.PEM) key_pem = pfx.private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption() ) return cert_pem, key_pem # 在setup_client中使用 cert_pem, key_pem = load_cert_from_windows("你的证书SHA1指纹") wsse = BinarySignature(certfile=cert_pem, key_file=key_pem)
4. 排查服务端响应结构
如果确实需要WSSE签名,开启Zeep调试日志,查看服务端返回的XML是否包含<soap:Header>:
import logging logging.basicConfig(level=logging.DEBUG)
如果响应中没有Header,要么是服务端不支持WSSE签名,要么需要联系服务端调整返回格式。也可以自定义签名验证类跳过Header检查,但这属于临时 hack,不推荐生产环境使用。
内容的提问来源于stack exchange,提问作者Russell Rozenbaum

