Windows上使用Python requests时SSL失败

15

非常抱歉文章太长了,但我真的在尽力详细说明...

我有一个专门的网站,用作远程服务器上运行的各种环境模型之间交换数据的桥梁,并且运行于不同类型的操作系统(Linux、MacOS和Windows)。基本上每个服务器都可以上传/下载数据文件到该网站,然后文件再由另一台服务器上的不同模型进行进一步处理。

该网站具有基本的安全保护措施(IP过滤、密码和使用LetsEncrypt证书的SSL)。所有远程服务器都可以通过我们创建的简单Web界面访问该网站并上传/下载数据。

现在我们正在尝试使用一个简单的Python(2.7)守护进程(基于requests模块)自动化一些交换。该守护进程监视某些文件夹并将内容上传到该网站。

该守护进程在所有远程服务器上都正常工作,除了一台运行Windows 7 Enterprise 64位的服务器。该服务器已安装Python 2.7.13和以下软件包:DateTime(4.1.1)、psutil(5.2.0)、pytz(2016.10)、requests(2.13.0)和zope.interface(4.3.3)。

从该服务器上,SSL连接通过Web浏览器可以正常工作,但是守护进程总是返回:

raise SSLError(e, request=request)
requests.exceptions.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:661)

以下是我们迄今为止尝试过的:

  • 将verify设置为false。这可以正常工作,但我们不能在最终生产环境中使用它。
  • 从另一台守护程序正常工作的服务器上复制证书,并将verify设置为证书文件名称(没有成功)
  • 将'User-agent'设置为与Windows机器上通过Web浏览器进行连接时获取的完全相同的字符串(没有成功)

我们应该在Windows服务器上查看哪些其他设置以尝试解决问题? 它可能是防火墙设置,可以通过浏览器的SSL连接传递,但阻止Python守护程序吗?

更新
出现错误的运行Windows远程服务器的组织在代理级别替换所有SSL证书。
他们的IT人员通过将我们网站的URL添加到其代理设置的“直通”站点列表中来解决了我们的问题。

这个方法可以工作,现在很好。 但是,我想知道是否可以直接在Python中处理证书替换...

2个回答

26

可以使用Python内置的ssl模块让Requests库来完成HTTP连接的SSL部分。这是可行的,因为Requests使用的urllib3 utils允许将Python SSLContext传递给它们。

但请注意,这可能取决于之前Windows访问所加载的必要证书已经被加载到信任存储区中(请参见此评论)。

以下是一些示例代码(需要最新版本的Requests;它适用于2.18.4):

import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.ssl_ import create_urllib3_context

class SSLContextAdapter(HTTPAdapter):
    def init_poolmanager(self, *args, **kwargs):
        context = create_urllib3_context()
        kwargs['ssl_context'] = context
        context.load_default_certs() # this loads the OS defaults on Windows
        return super(SSLContextAdapter, self).init_poolmanager(*args, **kwargs)

s = requests.Session()
adapter = SSLContextAdapter()
s.mount('https://myinternalsite', adapter)
response = s.get('https://myinternalsite')

4
对我没有用。我正在使用Requests v2.19.1,它给我报错:'PyOpenSSLContext' object has no attribute 'load_default_certs' - kraussian
9
对于Python 3.6.5和requests 2.19.1版本,我需要将create_urllib3_context的导入替换为import ssl,然后更改上下文的赋值为context = ssl.create_default_context() - jakob.j
@jakob.j 你提出了一个很好的观点... ssl create_default_context 方法实际上自动执行了 load_default_certs 操作,这是一种更简单的设置上下文的方法,而不必从 urllib3 导入私有成员。这似乎适用于 Python 2.7 和 Python 3。我对上述代码的测试也适用于 Python 3,但使用你的方法可能会更简单。 - David Fraser
1
我已经在Python3下运行成功,并仅尝试过对requests 2.12.4和2.19.1进行测试。由于@jakob.j提供了更好的解决方案,因此我删除了我的评论。 - Tom M.
1
@Josh,每次调用requests.get()等函数时都会在后台使用一个会话。因此,没有办法在没有会话的情况下使用“requests”。 - cowlinator
显示剩余6条评论

2

Requests不使用您的Windows根CA存储,就像浏览器一样。

从文档中可以看到: 默认情况下,Requests捆绑了一组根CA,它信任这些根CA来自Mozilla信任库。但是,这些仅在每个Requests版本中更新一次。

可以通过REQUESTS_CA_BUNDLE环境变量指定可信CA列表。

您可以直接执行以下操作:

cafile = 'cacert.pem' # http://curl.haxx.se/ca/cacert.pem
r = requests.get(url, verify=cafile)

如果您的CA证书由公共实体签署,您也可以使用certifi。


谢谢您的回复。我们已经尝试过这种方法(第二个要点),但没有成功。我编辑了我的问题以确保它更清楚。 - user6357781
你复制了哪个证书?你需要颁发证书机构的证书,而不是Web服务器证书。 - Artagel
我们从另一台Windows服务器复制了cacert.pem文件,该守护程序在那里工作。从C:\Python27\lib\site-packages\requests\cacert.pem。 - user6357781
如果相同的代码在另一台计算机上能够正常工作,那么问题可能出在文件上,例如复制过程中被损坏。否则我就不确定了。 - Artagel
我不确定你所说的“解决你的盒子上的根本问题”是什么意思...如果你读了我的问题,你会发现在我们尝试过的其他系统上一切都运行得很好。而且,与所有其他服务器一样,它无法工作的机器具有完全相同版本的Python和Requests(以及所有其他已安装的软件包)...所以我只是想弄清楚在这台机器上还应该查看什么其他问题。 - user6357781
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接