如何在Python请求中禁用主机名检查

13 投票
6 回答
37405 浏览
提问于 2025-04-18 00:39

我正在使用Requests库来连接一个RESTful API。我要连接的服务器使用了自签名证书的SSL。

cafile = "gateway.pem"
r = requests.get(request, auth=('admin', 'password'), verify=cafile)

问题是我遇到了主机名不匹配的SSLError错误。应该有办法在不关闭证书验证的情况下禁用主机名检查,就像许多Java实现那样,但我找不到在Python的Requests中怎么做到这一点。

错误追踪信息:

Traceback (most recent call last):
  File "<pyshell#43>", line 1, in <module>
    r = requests.get(request, auth=("admin", "password"), verify='gateway.pem')
  File "C:\Python27\lib\site-packages\requests-2.0.0-py2.7.egg\requests\api.py", line 55, in get
    return request('get', url, **kwargs)
  File "C:\Python27\lib\site-packages\requests-2.0.0-py2.7.egg\requests\api.py", line 44, in request
    return session.request(method=method, url=url, **kwargs)
  File "C:\Python27\lib\site-packages\requests-2.0.0-py2.7.egg\requests\sessions.py", line 357, in request
    resp = self.send(prep, **send_kwargs)
  File "C:\Python27\lib\site-packages\requests-2.0.0-py2.7.egg\requests\sessions.py", line 460, in send
    r = adapter.send(request, **kwargs)
  File "C:\Python27\lib\site-packages\requests-2.0.0-py2.7.egg\requests\adapters.py", line 358, in send
    raise SSLError(e)
SSLError: hostname '10.76.92.70' doesn't match u'lital.com'

这该怎么做呢?

6 个回答

-5

这个链接是关于Python中请求库的SSL证书验证的文档:http://docs.python-requests.org/en/latest/user/advanced/#ssl-cert-verification

verify这个参数其实是一个标志,用来表示是否验证证书,而不是用来提供证书文件的。你传入了一个非空字符串,这在布尔上下文中会被当作True来处理。

如果你想提供证书文件的路径,应该使用cert=这个参数,或者如果你想关闭验证,可以用verify=False

编辑:虽然文档上说你可以把CA路径传给verify=,但没有提供示例。如果能看到你收到的完整错误信息,那会很有帮助。

-4

看起来这个功能已经被添加到最新版本的requests库中了。我已经确认过了,对我来说是有效的——只需在请求中加上verify=False,就像文档中的例子那样:

requests.get('https://api.github.com', verify=False)
3

你有没有查看过 SSLContext.check_hostname 这个参数?你可以把它设置为 False,这样就不会检查主机名了:

context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
context.verify_mode = ssl.CERT_REQUIRED
context.check_hostname = True
context.load_default_certs()

唯一的限制是,这个方法只适用于 Python 3.4 及以后的版本。

参考资料: https://docs.python.org/3/library/ssl.html#ssl.SSLContext.check_hostname

8

我来得有点晚,不过如果你安装版本0.7.0或更新的版本,requests_toolbelt可能会对你有帮助(我用的ubuntu 16.04只有0.6.0):https://toolbelt.readthedocs.io/en/latest/adapters.html#hostheaderssladapter

从这个链接可以找到更多信息:

Example usage:
>>> s.mount('https://', HostHeaderSSLAdapter())
>>> s.get("https://93.184.216.34", headers={"Host": "example.org"})
10

Requests库本身不直接支持这个功能,不过你可以提供一个自定义的传输适配器,这样就能利用底层的urllib3的特性。关于如何使用传输适配器,可以参考requests的文档。

这段代码没有经过测试,但应该是可以工作的。

from requests.adapters import HTTPAdapter
from requests.packages.urllib3.poolmanager import PoolManager


# Never check any hostnames
class HostNameIgnoringAdapter(HTTPAdapter):
    def init_poolmanager(self, connections, maxsize, block=False):
        self.poolmanager = PoolManager(num_pools=connections,
                                       maxsize=maxsize,
                                       block=block,
                                       assert_hostname=False)


# Check a custom hostname
class CustomHostNameCheckingAdapter(HTTPAdapter):
    def cert_verify(self, conn, url, verify, cert):
        #      implement me
        host = custom_function_mapping_url_to_hostname(url)
        conn.assert_hostname = host
        return super(CustomHostNameCheckingAdapter,
                     self).cert_verify(conn, url, verify, cert)

具体来说,assert_hostname这个参数的作用如下:如果设置为None,就会使用URL中的主机名;如果设置为False,则会关闭主机名检查;如果设置为一个自定义字符串,则会根据这个字符串进行验证。

撰写回答