使用keytools或者openssl生成p12格式的KeyStore(包含SSL证书),并使用该证书和SpringBoot搭建了服务端的https接口,搭建过程参考HTTPS相关知识点介绍
接下来介绍如何在Pyhon中使用requests工具包调用服务端的https接口。
第1次尝试-直接调用
import requests
if __name__ == '__main__':LOCAL_SERVICE_URL = 'https://localhost:1443/api/anno/success'REMOTE_SERVICE_URL = 'https://www.baidu.com/'print('\n\n-----request remote https-----')res = requests.get(REMOTE_SERVICE_URL)print(res.status_code)print('\n\n-----request local https-----')res = requests.get(LOCAL_SERVICE_URL)print(res.status_code)
运行结果:调用REMOTE_SERVICE_URL
成功,调用LOCAL_SERVICE_URL
抛出异常:ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self signed certificate (_ssl.c:1131)
。
结果分析:requests
工具包进行SSL验证时依赖certifi
工具包,certifi
包安装目录下的cacert.pem
文件中预置了可信任的根证书颁布机构清单,默认情况下只有清单中机构及其下层机构颁布的SSL证书可以通过验证。REMOTE_SERVICE_URL
使用的SSL证书是正式CA机构颁布的,其根证书机构为:GlobalSign Root CA,该机构就在certifi
包可信任的根证书CA机构清单中,所以调用REMOTE_SERVICE_URL
成功。而LOCAL_SERVICE_URL
使用的自颁布SSL证书,不在可信任机构清单中,所以验证失败抛出异常。
解决办法:一种是跳过SSL证书验证,一种是将自颁发证书的机构设置为可信任的。
第2次尝试-跳过SSL验证
根据requests的官方文档,虽然不推荐,但是可以通过设置请求参数verify=False
来跳过SSL验证。
import requests
from requests.packages import urllib3
if __name__ == '__main__':LOCAL_SERVICE_URL = 'https://localhost:1443/api/anno/success'REMOTE_SERVICE_URL = 'https://www.baidu.com/'# 屏蔽requests关闭SSL证书验证(verify参数设置为False)时的告警信息urllib3.disable_warnings()print('\n\n-----request remote https-----')res = requests.get(REMOTE_SERVICE_URL)print(res.status_code)print('\n\n-----request local https-----')res = requests.get(LOCAL_SERVICE_URL, verify=False)print(res.status_code)
第3次尝试-将自颁发证书的机构设置为可信任-临时设置
根据requests的官方文档,可以将请求参数verify
设置为 CA_BUNDLE 文件的路径或者包含可信任 CA 证书文件的文件夹路径(如果 verify
设为文件夹路径,文件夹必须通过 OpenSSL 提供的 c_rehash 工具处理),这样也可以通过SSL证书验证。
根据博客What is a CA Bundle and Where to Find It?的说明,CA Bundle是包含根证书和中间证书的一个文件,由于自颁发SSL证书只有一层,所以自颁发的SSL证书就是CA Boundle文件。
requests
的verify
参数要求CA Bundle文件是文本格式,二进制的无法读取。下面介绍获取文本格式SSL证书的两种方法:
- 由于p12格式的KeyStore包含了SSL证书和私钥,所以需要从中提取SSL证书,采用openssl的python工具包将p12格式转换为pem格式
import os
from OpenSSL import crypto
def p12_to_pem(cert_name, pwd):pem_file_path = cert_name + '.pem'pem_file = open(pem_file_path, 'wb')p12_file_path = cert_name + '.p12'p12_file = crypto.load_pkcs12(open(p12_file_path, 'rb').read(), pwd)
print(crypto.dump_privatekey(crypto.FILETYPE_PEM, p12_file.get_privatekey()))print(crypto.dump_certificate(crypto.FILETYPE_PEM, p12_file.get_certificate()))
pem_file.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, p12_file.get_privatekey()))pem_file.write(crypto.dump_certificate(crypto.FILETYPE_PEM, p12_file.get_certificate()))ca = p12_file.get_ca_certificates()if ca is not None:for cert in ca:pem_file.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))pem_file.close()return pem_file_path
if __name__ == '__main__':root_path = 'E:\dataPython\data\cert'cert_pem_file_path = p12_to_pem(os.path.join(root_path, 'baeldung'), b'****')
生成pem格式的SSL证书文件baeldung.pem
内容如下:其中PRIVATE KEY其实不是SSL证书的内容,删除后也不影响证书验证。
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCN1bHqnX4kceSh
wSlb4s8X7Hz+581Kyq2tPDBSwqe6b9SmC5Hq0m8EbsChy/OwM9FrZZF9bOdPHHUM
sCtl3EEmc0fHylHqBT9/JWM=
-----END PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIDgjCCAmqgAwIBAgIEZacX2zANBgkqhkiG9w0BAQsFADBiMQswCQYDVQQGEwJD
TjESMBAGA1UECBMJZ3Vhbmdkb25nMREwDwYDVQQHEwhzaGVuemhlbjENMAsGA1UE
9o2PulnUgwok+63gcbkCA1In7tz+qylx/bhz8qT0eI6WpsPYc0Y=
-----END CERTIFICATE-----
- 除了使用openssl将p12转换为pem格式的SSL证书外,可以使用浏览器功能导出证书。在浏览器访问服务端的https接口后,按以下步骤即可导出cer格式的SSL证书。
导出的SSL证书文件baeldung.cer
内容如下:只有证书内容,没有PRIVATE KEY。
-----BEGIN CERTIFICATE-----
MIIDgjCCAmqgAwIBAgIEZacX2zANBgkqhkiG9w0BAQsFADBiMQswCQYDVQQGEwJD
TjESMBAGA1UECBMJZ3Vhbmdkb25nMREwDwYDVQQHEwhzaGVuemhlbjENMAsGA1UE
9o2PulnUgwok+63gcbkCA1In7tz+qylx/bhz8qT0eI6WpsPYc0Y=
-----END CERTIFICATE-----
最后设置requests
的verify
为CA BUNDLE文件路径来解决证书验证不通过的问题,用pem或cer格式的都可以。
import requests
from requests.packages import urllib3
if __name__ == '__main__':LOCAL_SERVICE_URL = 'https://localhost:1443/api/anno/success'REMOTE_SERVICE_URL = 'https://www.baidu.com/'print('\n-----request remote https-----')res = requests.get(REMOTE_SERVICE_URL)print(res.status_code)print('\n-----request local https-----')res = requests.get(LOCAL_SERVICE_URL, verify='E:/dataPython/data/cert/baeldung.pem')print(res.status_code)res = requests.get(LOCAL_SERVICE_URL, verify='E:/dataPython/data/cert/baeldung.cer')print(res.status_code)
注:对于https://www.baidu.com/
其SSL证书有3层,所以仅仅导出某一层的SSL证书是不能作为CA BUNDLE文件的,否则反而会报错。必须将3层证书按最底层到最高层(ROOT)的顺序拷贝到1个文件后,该文件才能作为CA BUNDLE文件。即按照博客What is a CA Bundle and Where to Find It?所说的操作。
第4次尝试-将自颁发证书的机构设置为可信任-永久设置
根据第1次尝试的结果分析,证书校验时从certifi
包安装目录下的cacert.pem
文件获取可信任的根证书颁发机构清单,那我们可以把我们自颁发的证书内容追加到cacert.pem
文件来一次性解决SSL证书验证问题。追加内容后的cacert.pem
文件如下:
# Issuer: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA
# Subject: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA
# Label: "GlobalSign Root CA"
# Serial: 4835703278459707669005204
# MD5 Fingerprint: 3e:45:52:15:09:51:92:e1:b7:5d:37:9f:b1:87:29:8a
# SHA1 Fingerprint: b1:bc:96:8b:d4:f4:9d:62:2a:a8:9a:81:f2:15:01:52:a4:1d:82:9c
# SHA256 Fingerprint: eb:d4:10:40:e4:bb:3e:c7:42:c9:e3:81:d3:1e:f2:a4:1a:48:b6:68:5c:96:e7:ce:f3:c1:df:6c:d4:33:1c:99
-----BEGIN CERTIFICATE-----
MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG
HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==
-----END CERTIFICATE-----
# Issuer: CN=HARICA TLS ECC Root CA 2021 O=Hellenic Academic and Research Institutions CA
# Subject: CN=HARICA TLS ECC Root CA 2021 O=Hellenic Academic and Research Institutions CA
# Label: "HARICA TLS ECC Root CA 2021"
# Serial: 137515985548005187474074462014555733966
# MD5 Fingerprint: ae:f7:4c:e5:66:35:d1:b7:9b:8c:22:93:74:d3:4b:b0
# SHA1 Fingerprint: bc:b0:c1:9d:e9:98:92:70:19:38:57:e9:8d:a7:b4:5d:6e:ee:01:48
# SHA256 Fingerprint: 3f:99:cc:47:4a:cf:ce:4d:fe:d5:87:94:66:5e:47:8d:15:47:73:9f:2e:78:0f:1b:b4:ca:9b:13:30:97:d4:01
-----BEGIN CERTIFICATE-----
MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQsw
nxS2PFOiTAZpffpskcYqSUXm7LcT4Tps
-----END CERTIFICATE-----
#省略其他ROOT CA
# SelfSigned
-----BEGIN CERTIFICATE-----
MIIDgjCCAmqgAwIBAgIEZacX2zANBgkqhkiG9w0BAQsFADBiMQswCQYDVQQGEwJD
TjESMBAGA1UECBMJZ3Vhbmdkb25nMREwDwYDVQQHEwhzaGVuemhlbjENMAsGA1UE
9o2PulnUgwok+63gcbkCA1In7tz+qylx/bhz8qT0eI6WpsPYc0Y=
-----END CERTIFICATE-----
再次运行第1次尝试的代码来调用https借口,访问都正常。
总结
第2、3、4次尝试中使用的三种解决方法,推荐采用第3次尝试使用的设置verify参数为CA BUNDLE文件路径,安全性更好。