FastAPI 按环境条件控制 OpenAPI 文档:基于 Settings 与环境变量动态开关 /docs 与 /openapi.json
【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi
在 FastAPI 中,OpenAPI 模式(/openapi.json)和交互式文档界面(/docs、/redoc)是否暴露,完全由FastAPI()构造参数决定。本文介绍如何结合pydantic-settings的BaseSettings与环境变量,在不同部署环境(如生产环境)下条件化地开启、重定向或彻底关闭 OpenAPI 及相关文档端点,并结合 FastAPI 核心源码 说明其底层路由注册机制与验证测试,帮助你写出“一套代码、按环境切换文档可见性”的部署方案。
先说清安全边界:隐藏文档不等于保护 API
官方文档在给出操作之前,先划定了这条功能的安全边界,这一点在实施任何隐藏方案前都应当明确:
- 隐藏文档界面不应该成为保护 API 的主要手段。Path operations(路径操作)本身仍然在原来的地址上可以被访问,隐藏
/docs和/openapi.json并没有给 API 增加任何实质性的安全层;如果代码本身存在安全漏洞,它依然存在。 - 隐藏文档的副作用是负面的:它让外部(包括你自己)更难理解如何与 API 交互,也可能让你在生产环境排障时更加困难。从安全视角看,这基本属于 Security through obscurity(依靠隐蔽性实现安全) 的范畴。
如果你想真正加固 API,官方文档建议做的是:
- 为请求体和响应体定义清晰的 Pydantic 模型;
- 通过依赖项(dependencies)配置必要的权限和角色;
- 绝不存储明文密码,只存储密码哈希;
- 实现并使用成熟的密码学工具,如 pwdlib、JWT 令牌等;
- 在需要的地方用 OAuth2 scopes 增加更细粒度的权限控制;
- 等等。
尽管如上所述,仍然可能存在非常具体的使用场景:你需要在某些环境(例如生产环境)真正关闭 API 文档,或者根据环境变量中的配置来决定文档是否开放。条件化 OpenAPI 就是为这类场景服务的。
用 Settings 与环境变量条件化配置 OpenAPI
核心思路是:把openapi_url放进pydantic-settings的Settings类中,再把它传给FastAPI()构造器。pydantic-settings是 FastAPI 官方推荐的配置管理依赖(见 pyproject.toml 中的pydantic-settings >=2.0.0条目),它会按“环境变量 > 其他来源 > 默认值”的优先级解析配置,因此通过环境变量即可覆盖默认值。
官方教程的完整示例代码位于 docs_src/conditional_openapi/tutorial001_py310.py:
from fastapi import FastAPI from pydantic_settings import BaseSettings class Settings(BaseSettings): openapi_url: str = "/openapi.json" settings = Settings() app = FastAPI(openapi_url=settings.openapi_url) @app.get("/") def root(): return {"message": "Hello World"}这段代码的工作方式:
Settings声明了openapi_url字段,默认值为"/openapi.json",与FastAPI()的内置默认值保持一致;BaseSettings(即pydantic-settings的BaseSettings)在实例化settings = Settings()时会自动读取名为OPENAPI_URL的环境变量(字段名大写映射)来覆盖默认值;FastAPI(openapi_url=settings.openapi_url)把解析后的值传给应用,从而决定 OpenAPI 与文档 UI 端点的挂载行为。
由于openapi_url的默认值是"/openapi.json",不设置任何环境变量时,应用行为与默认完全相同:/openapi.json、/docs、/redoc均可正常访问。
通过空字符串环境变量彻底禁用 OpenAPI 与文档 UI
pydantic-settings会把Settings字段声明为str类型,因此要“禁用”文档,不能简单地传None,而是把环境变量OPENAPI_URL设置为空字符串:
$ OPENAPI_URL= uvicorn main:app <span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)注意OPENAPI_URL=后面没有任何值——这是 bash 中“把变量设为空字符串”的写法。启动后,访问/openapi.json、/docs、/redoc任意一个 URL,都会得到404 Not Found:
{ "detail": "Not Found" }也就是说,业务端点(如/)不受影响,只有文档相关的三个默认端点被移除。
源码级原理:setup()中基于openapi_url的条件路由注册
为什么一个空字符串就能“一键关闭”三个端点?这由 fastapi/applications.py 中FastAPI.setup()方法的实现决定(约在 L1105-L1158):
def setup(self) -> None: if self.openapi_url: async def openapi(req: Request) -> JSONResponse: ... return JSONResponse(schema) self.add_route(self.openapi_url, openapi, include_in_schema=False) if self.openapi_url and self.docs_url: async def swagger_ui_html(req: Request) -> HTMLResponse: ... self.add_route(self.docs_url, swagger_ui_html, include_in_schema=False) ... if self.openapi_url and self.redoc_url: async def redoc_html(req: Request) -> HTMLResponse: ... self.add_route(self.redoc_url, redoc_html, include_in_schema=False)关键点:
openapi_url是所有文档端点的总开关。setup()中对三个端点的add_route调用都以self.openapi_url为前置条件(if self.openapi_url:、if self.openapi_url and self.docs_url:、if self.openapi_url and self.redoc_url:)。由于 Python 中空字符串""是 falsy 值,设置OPENAPI_URL=后,OpenAPI 端点、Swagger UI(/docs)和 ReDoc(/redoc)三条路由都不会被注册,请求自然落到 Starlette 的默认 404 处理逻辑,返回{"detail": "Not Found"}。/docs和/redoc本身不是独立开关的“主开关”。从 fastapi/applications.py 的参数文档可以看到,docs_url和redoc_url虽然也可以单独设为None来禁用各自端点,但它们的文档明确写着“Ifopenapi_urlis set toNone, this will be automatically disabled”——即openapi_url一旦为 falsy,文档 UI 必然随之消失。这解释了为什么环境变量方案只操作OPENAPI_URL一个变量就足够。setup()的调用时机。在 fastapi/applications.py 中,self.setup()在FastAPI.__init__结束时被调用,也就是说路由注册发生在应用构造阶段。因此Settings()也必须在FastAPI()之前实例化,环境变量才会被正确读到——这也是示例代码中settings = Settings()一行位于FastAPI()之前的原因。- 与
openapi_url同级的相关参数还包括swagger_ui_oauth2_redirect_url(默认/docs/oauth2-redirect,仅在 Swagger UI 使用 OAuth2 授权按钮时生效),它也受openapi_url and docs_url的注册条件约束。
如果你不想禁用而只是想移动文档位置,同样的 Settings 模式也适用:例如设置OPENAPI_URL=/api/v1/openapi.json,OpenAPI 模式就会改挂到新路径上(/docs、/redoc仍按docs_url/redoc_url独立挂载,但会引用新的openapi_url地址——从setup()中openapi_url = root_path + self.openapi_url的拼接逻辑可以看出 UI 页面始终动态指向当前配置的模式地址)。
测试用例如何验证这套机制
官方为这个教程示例提供了专门的测试文件 tests/test_tutorial/test_conditional_openapi/test_tutorial001.py,其验证方式值得在自写项目测试中借鉴:
def test_disable_openapi(monkeypatch): monkeypatch.setenv("OPENAPI_URL", "") # Load the client after setting the env var client = get_client() response = client.get("/openapi.json") assert response.status_code == 404, response.text response = client.get("/docs") assert response.status_code == 404, response.text response = client.get("/redoc") assert response.status_code == 404, response.text两个值得注意的细节:
- 环境变量必须在模块加载(即
Settings()实例化和FastAPI()构造)之前设置。测试中先monkeypatch.setenv("OPENAPI_URL", ""),再调用get_client(),而get_client()内部使用importlib.reload(tutorial001_py310)强制重新导入模块,确保Settings()重新读取环境变量后重新构造应用。如果顺序颠倒(先加载应用再设变量),配置不会生效。 - 同一测试文件还包含
test_root()(验证/业务端点在禁用 OpenAPI 后仍返回200和{"message": "Hello World"})以及test_default_openapi()(验证不设环境变量时/docs、/redoc返回 200,且/openapi.json返回结构完整、"openapi": "3.1.0"的模式快照),与禁用场景形成对照,保证“默认开启、显式禁用”两个方向都被覆盖。
适用前提与注意事项
- 依赖前提:该方案依赖
pydantic-settings(BaseSettings)。在 pyproject.toml 中它是作为settings可选依赖组声明的(pydantic-settings >=2.0.0),如果你的项目只安装了核心fastapi,需要额外安装pydantic-settings。 - 类型限制:示例中
openapi_url: str = "/openapi.json"声明为str类型,所以禁用手段是空字符串而非None(pydantic-settings对str | None字段处理None的行为与空字符串不同,且此处源码判断依据的是 falsy 值,空字符串即可触发禁用)。 - 仅影响文档端点:条件化
openapi_url移除的只是/openapi.json、/docs、/redoc(及 OAuth2 重定向端点)这几条元数据路由,所有业务 path operations 照常工作;请再次记住前文的安全边界——隐藏文档本身不构成安全加固。 - 可组合使用:
docs_url、redoc_url等参数同样可以通过 Settings 暴露,实现更细粒度的控制(例如生产环境保留/openapi.json供内部工具使用、仅关闭 Swagger UI),其原理与上述setup()中的注册条件一致。
小结
FastAPI 的条件化 OpenAPI 方案可以概括为三步:用pydantic-settings的BaseSettings声明openapi_url并保留默认值"/openapi.json";在FastAPI()构造时传入settings.openapi_url;在需要关闭文档的环境中以OPENAPI_URL= uvicorn main:app的形式注入空字符串。其底层机制是 fastapi/applications.py 中setup()对openapi_url的 falsy 判断统一控制三条文档路由的注册,且由于setup()在应用构造阶段执行,环境变量必须在应用加载前就位。配合官方测试中“先设环境变量、后重载模块”的验证范式,你可以放心地把文档可见性作为部署配置的一部分来管理,同时牢记:真正保护 API 的始终是权限模型、校验与安全的凭据处理,而不是隐藏文档。
【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考