使用requests.post携带PFX证书发送JSON遇401未授权问题
使用Python的requests.post向API发送JSON对象并附加PFX格式SSL证书时,服务器始终返回401未授权错误,错误信息如下:
"status":"401",
"message":"{"error":"unauthorized","error_description":"Not valid credentials supplied. Please check request parameters."}"
确认JSON对象构造无误,相关代码如下:
from contextlib import contextmanager from pathlib import Path from tempfile import NamedTemporaryFile import json import requests from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption from cryptography.hazmat.primitives.serialization.pkcs12 import load_key_and_certificates cert_file = "certificado/cert.pfx" cert_password = "xxxxx" headers = { 'accept': 'application/json' } @contextmanager def pfx_to_pem(pfx_path, pfx_password): ''' Decrypts the .pfx file to be used with requests. ''' pfx = Path(pfx_path).read_bytes() private_key, main_cert, add_certs = load_key_and_certificates(pfx, pfx_password.encode('utf-8'), None) with NamedTemporaryFile(suffix='.pem', delete=False) as t_pem: with open(t_pem.name, 'wb') as f_pem: f_pem.write(private_key.private_bytes(Encoding.PEM, PrivateFormat.PKCS8, NoEncryption())) f_pem.write(main_cert.public_bytes(Encoding.PEM)) for ca in add_certs: f_pem.write(ca.public_bytes(Encoding.PEM)) yield t_pem.name jsonObj = {"Things to send"} myURL = "https://myurl.com" with pfx_to_pem(cert_file, cert_password) as cert: response = requests.post(myURL, json=jsonObj, cert=cert, headers= headers) if response.status_code == 200: response= json.loads(response.text) print("Response OK!") print(response) else: print(response.status_code, response.text)
验证PFX转PEM的完整性
用openssl pkcs12 -in cert.pfx -out cert.pem -nodes命令手动转换PFX文件,对比代码生成的PEM文件内容是否一致。如果手动转换后请求能成功,说明代码里的转换逻辑存在问题。调整
cert参数的使用方式requests的cert参数支持两种形式:单个文件路径(需同时包含私钥和证书),或者元组形式(分别传入证书路径和私钥路径)。尝试将转换后的内容拆分为单独的证书文件和私钥文件,用cert=(cert_path, key_path)的方式传入,部分服务器对这种形式的兼容性更好。另外,注意证书链的顺序,有些服务器要求先写CA证书,再写用户证书,最后写私钥。检查是否需要额外身份验证
部分API除了SSL客户端证书,还要求在请求头中添加API密钥或Basic Auth。查看API文档,确认是否需要额外的Authorization头,比如headers['Authorization'] = 'Bearer {token}',或者生成Basic Auth的编码值添加到请求头。确认JSON请求体的准确性
用json.dumps(jsonObj)打印实际发送的JSON字符串,对比API要求的字段名、嵌套结构、必填项是否完全匹配。requests.post的json参数会自动设置Content-Type: application/json头,但如果服务器有特殊要求,可手动显式添加该头到headers中。开启请求调试日志
开启调试日志查看请求细节,确认证书是否正确加载、请求头和请求体是否符合要求:import logging import http.client http.client.HTTPConnection.debuglevel = 1 logging.basicConfig() logging.getLogger().setLevel(logging.DEBUG) requests_log = logging.getLogger("requests.packages.urllib3") requests_log.setLevel(logging.DEBUG) requests_log.propagate = True运行代码后查看日志,定位请求中的异常点。
内容的提问来源于stack exchange,提问作者Francisco Marques

