Skip to content

Requests - SSL 认证

现代 Web 通信严重依赖 HTTPS(HTTP 安全)来保护传输中的数据。这种安全性由 SSL/TLS(Secure Sockets Layer/Transport Layer Security,安全套接字层/传输层安全)证书提供。当您使用 Requests 库与 HTTPS URL 交互时,它通过默认验证服务器的 SSL 证书,在确保这种安全性方面起着至关重要的作用。

此验证过程主要检查两件事:1) 证书是否由可信的证书颁发机构(Certificate Authority, CA)颁发。2) URL 中的主机名(hostname)是否与证书中的主机名匹配。这有助于防止“中间人(man-in-the-middle)”攻击。

默认情况下,Requests 会尝试验证所有 HTTPS 请求的 SSL 证书。它使用与 certifi 包捆绑的一组可信根 CA,certifi 通常作为 Requests 的依赖项安装。

如果证书有效且受信任,请求将顺利进行:

import requests
# Example: Fetching data from a secure API endpoint
# 示例:从安全的 API 端点获取数据
URL = 'https://jsonplaceholder.typicode.com/users/1'
try:
response = requests.get(URL)
response.raise_for_status() # Raise an exception for bad status codes (4XX or 5XX)
# 如果状态码不是 2XX,则抛出异常 (4XX 或 5XX)
print('Successfully fetched data:') # 成功获取数据:
print(response.json()) # Assuming the response is JSON - 假设响应是 JSON
except requests.exceptions.SSLError as e:
print(f'SSL Error occurred while accessing {URL}: {e}') # 访问 {URL} 时发生 SSL 错误:{e}
except requests.exceptions.RequestException as e:
print(f'Request failed for {URL}: {e}') # 请求 {URL} 失败:{e}
Successfully fetched data:
{
'id': 1,
'name': 'Leanne Graham',
'username': 'Bret',
'email': 'Sincere@april.biz',
'address': { # ... other address details ... # ... 其他地址详细信息 ...
},
'phone': '1-770-736-8031 x56442',
'website': 'hildegard.org',
'company': { # ... other company details ... # ... 其他公司详细信息 ...
}
# ... (rest of the JSON data, truncated for brevity) # ... (JSON 数据的其余部分,为简洁起见已截断)
}

如果 requests 无法验证证书(例如,它是自签名证书且不在信任存储中、已过期或主机名不匹配),它将引发 requests.exceptions.SSLError 异常。

禁用 SSL 验证(务必极其谨慎使用)

Section titled “禁用 SSL 验证(务必极其谨慎使用)”

在某些特定场景下,例如针对使用自签名证书的开发服务器进行测试时,您可能需要禁用 SSL 验证。您可以通过向请求方法传递 verify=False 来实现。

警告: 禁用 SSL 验证会使您的连接不安全,并使您的应用程序面临安全风险,包括中间人攻击。这只应在受控、受信任的环境中进行,绝不能用于生产环境中的外部服务。

import requests
import warnings
# It's generally better to import the specific warning from urllib3 directly
# 通常最好直接从 urllib3 导入特定的警告
from urllib3.exceptions import InsecureRequestWarning
# For demonstration, let's try a site known for having a self-signed certificate
# 为了演示,我们尝试一个已知使用自签名证书的网站
URL_SELF_SIGNED = 'https://self-signed.badssl.com/'
# Suppress only the InsecureRequestWarning for this example.
# In a real application, address the SSL issue or accept the risk explicitly.
# 对于本示例,仅抑制 InsecureRequestWarning。
# 在实际应用中,请解决 SSL 问题或明确接受风险。
warnings.simplefilter('ignore', InsecureRequestWarning)
try:
print(f'Attempting request to {URL_SELF_SIGNED} with verify=False')
# This request would fail with SSLError if verify=True (default)
# 如果 verify=True(默认值),此请求将因 SSLError 而失败
response = requests.get(URL_SELF_SIGNED, verify=False, timeout=5)
print(f'Request to {URL_SELF_SIGNED} successful with verify=False (Status: {response.status_code})')
print('Note: An InsecureRequestWarning was suppressed for this demonstration.')
# If you need to see the warning, comment out the warnings.simplefilter line above.
# 如果您需要看到警告,请注释掉上面的 warnings.simplefilter 那行。
except requests.exceptions.SSLError as e:
print(f'SSL Error (this should not happen with verify=False): {e}') # SSL 错误(在使用 verify=False 时不应发生):{e}
except requests.exceptions.RequestException as e:
print(f'Request failed for {URL_SELF_SIGNED}: {e}') # 请求 {URL_SELF_SIGNED} 失败:{e}
# Example of what happens if you try without verify=False (and without handling the warning)
# 示例:如果不使用 verify=False(且不处理警告)会发生什么
# print('\nAttempting request with default verification (expected to fail for self-signed.badssl.com):') # 尝试使用默认验证进行请求(预计对 self-signed.badssl.com 会失败):
# try:
# response_fail = requests.get(URL_SELF_SIGNED, timeout=5)
# except requests.exceptions.SSLError as e:
# print(f'As expected, SSL Error: {e}') # 正如预期,SSL 错误:{e}

当使用 verify=False 时,Requests 通过 urllib3 会发出一个 InsecureRequestWarning(不安全请求警告)。强烈建议解决潜在的 SSL 证书问题,而不是在生产环境中常规性地禁用验证或抑制此警告。

Attempting request to https://self-signed.badssl.com/ with verify=False
Request to https://self-signed.badssl.com/ successful with verify=False (Status: 200)
Note: An InsecureRequestWarning was suppressed for this demonstration.

有时,您需要与一个服务器通信,该服务器的证书由不在公共信任存储中的私有或内部证书颁发机构(CA)签名(例如,在企业环境中)。在这种情况下,您可以告诉 Requests 信任此 CA,方法是提供其证书 bundle 文件(一个包含一个或多个 PEM 格式 CA 证书的文件)的路径。

您将此 CA bundle 文件的路径传递给 verify 参数。

import requests
# This is a conceptual example. You would replace the URL and path with your actual values.
# 这是一个概念性示例。您需要将 URL 和路径替换为您的实际值。
# url_internal_service = 'https://internal.example.com/api/data'
# path_to_custom_ca_bundle = '/path/to/your/company-ca-bundle.pem'
# print(f"Conceptual request to {url_internal_service} using custom CA:")
# print(f"response = requests.get(url_internal_service, verify='{path_to_custom_ca_bundle}')")
# To make this runnable, we'll just show the concept:
# 为了使其可运行,我们只展示概念:
print('Conceptual example for using a custom CA bundle:') # 使用自定义 CA bundle 的概念性示例:
print("response = requests.get('https://your-internal-service.com/data', verify='/path/to/custom_ca.pem')")
# Example using httpbin.org, which doesn't require a custom CA, but shows syntax:
# If you had a local CA cert for 'httpbin.org' (which you wouldn't normally):
# 示例使用 httpbin.org,它不需要自定义 CA,但展示了语法:
# 如果您有一个用于 'httpbin.org' 的本地 CA 证书(通常不需要):
# try:
# response = requests.get('https://httpbin.org/get', verify='/path/to/a/valid/ca_for_httpbin.pem')
# print('Request to httpbin.org with (hypothetical) custom CA successful.') # 使用(假设的)自定义 CA 成功请求 httpbin.org。
# except requests.exceptions.SSLError as e:
# print(f'SSL Error with custom CA for httpbin.org (as expected if path is invalid/not needed): {e}')
# 使用自定义 CA 请求 httpbin.org 时出现 SSL 错误(如果路径无效/不需要,这是预期的):{e}

如果服务器的证书针对提供的 CA bundle 成功通过验证,请求将继续。否则,通常会引发 SSLError,表明无法使用自定义 CA 进行证书验证。

对于更高级的场景,例如使用客户端 SSL 证书进行双向 TLS 身份验证(mutual TLS authentication),Requests 允许您使用 cert 参数传递客户端证书和私钥的路径(例如,cert=('/path/to/client.crt', '/path/to/client.key'))。请查阅官方 Requests 文档以获取更多详细信息。

如果您遇到 SSL 错误(requests.exceptions.SSLError):

  • 过时的 CA Bundle: 确保您的 certifi 包(Requests 通常用于 CA 的包)是最新版本:pip install --upgrade certifi requests。
  • 自签名证书: 如果这是您控制的服务器(例如,开发环境),可以将其 CA 安装到您的信任存储中,将其 CA 提供给 verify 参数,或者(最不安全,仅用于受信任的本地开发)使用 verify=False。
  • 主机名不匹配: URL 中的主机名必须与服务器证书中的通用名(Common Name, CN)或主题备用名称(Subject Alternative Name, SAN)匹配。
  • 缺少中间证书: 服务器可能未发送完整的证书链(certificate chain)。这是服务器配置问题。
  • 系统时钟: 确保您的系统时钟准确同步。SSL 证书有有效期。
  • 代理/防火墙: 企业代理或防火墙可能会拦截 SSL 流量并用自己的证书重新签名。您可能需要信任代理的 CA 证书。

您可以通过在终端中运行 python -m certifi 来找到 certifi 使用的 CA bundle 的路径。这对于诊断问题很有帮助。

理解并正确配置 SSL/TLS 验证对于构建安全的应用程序至关重要。请始终优先考虑安全性。