FastAPI集成ForgeRock OAuth2时SSL证书验证失败问题排查
问题场景
在公司内部环境基于FastAPI实现ForgeRock作为身份提供商的OAuth2认证,已获取必要证书,但调用/auth/login端点时遇到SSL证书验证错误:
[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self signed certificate in certificate chain (_ssl.c:997)
FastAPI运行在容器中,Dockerfile配置如下:
FROM xxx.com/xxx/python3.10.9-slim:1.0 WORKDIR /app COPY ./ /app COPY ./certs /etc/ssl/certs/ca-certificates RUN chmod 644 /etc/ssl/certs/ca-certificates/* RUN apt-get update && apt-get install -y ca-certificates RUN update-ca-certificates ...
证书已正确合并到/etc/ssl/certs/ca-certificates.crt。
代码中尝试加载自定义CA证书到SSL上下文,并通过自定义httpx.AsyncClient传入,但调试日志显示httpx仍在使用默认的certifi证书:
DEBUG:httpx:load_verify_locations cafile='/usr/local/lib/python3.10/site-packages/certifi/cacert.pem'
问题原因
- AuthLib客户端初始化逻辑覆盖:通过
client_kwargs传入的httpx_client实例,在AuthLib的OAuth客户端内部可能被重新初始化,导致自定义的verify参数被覆盖。 - 元数据请求未使用自定义客户端:请求
server_metadata_url获取ForgeRock配置时,AuthLib可能使用单独的默认客户端,而非你传入的自定义客户端。
解决方案
方案1:直接通过client_kwargs传递verify参数(推荐)
无需自定义httpx.AsyncClient,直接在oauth.register的client_kwargs中指定CA证书路径,AuthLib会自动将该参数传递给所有内部httpx请求:
CA_BUNDLE_PATH = '/etc/ssl/certs/ca-certificates.crt' oauth = OAuth() oauth.register( name='forgerock', client_id='client-id', client_secret='secret', server_metadata_url='https://forgerock-login', client_kwargs={ 'scope': 'openid profile email', 'verify': CA_BUNDLE_PATH # 直接指定自定义CA证书路径 } )
方案2:确保自定义客户端被全局使用
如果必须使用自定义SSL上下文,直接创建配置好的客户端实例,通过lambda传递给AuthLib,避免被重新初始化:
import ssl import httpx from authlib.integrations.starlette_client import OAuth CA_BUNDLE_PATH = '/etc/ssl/certs/ca-certificates.crt' ssl_context = ssl.create_default_context(cafile=CA_BUNDLE_PATH) # 预创建配置好的AsyncClient实例 custom_httpx_client = httpx.AsyncClient(verify=ssl_context) oauth = OAuth() oauth.register( name='forgerock', client_id='client-id', client_secret='secret', server_metadata_url='https://forgerock-login', client_kwargs={ 'scope': 'openid profile email', }, # 用lambda返回预创建的客户端,避免AuthLib重新初始化 httpx_client=lambda: custom_httpx_client )
方案3:全局覆盖CA证书路径(兜底)
如果上述方法无效,直接设置环境变量覆盖httpx和requests的默认CA路径,全局生效:
import os CA_BUNDLE_PATH = '/etc/ssl/certs/ca-certificates.crt' # 同时覆盖requests和httpx的默认CA配置 os.environ['REQUESTS_CA_BUNDLE'] = CA_BUNDLE_PATH os.environ['HTTPX_CA_BUNDLE'] = CA_BUNDLE_PATH
验证方法
添加日志确认httpx使用的CA路径:
import httpx import logging logging.debug(f"httpx默认CA路径: {httpx.DEFAULT_CA_BUNDLE_PATH}")
修改后日志应显示你指定的/etc/ssl/certs/ca-certificates.crt。
内容的提问来源于stack exchange,提问作者aozk

