首页
/ FastAPI 认证错误状态码迁移指南:将 401 恢复为旧版 403 的兼容方案

FastAPI 认证错误状态码迁移指南:将 401 恢复为旧版 403 的兼容方案

2026-09-07 23:06:10作者:裴麒琰

自 FastAPI 0.122.0 起,内置安全工具(security utilities)在认证失败后返回的错误状态码从 403 Forbidden 调整为更符合 HTTP 规范的 401 Unauthorized,并附带标准的 WWW-Authenticate 响应头。本文面向依赖旧行为(403)的存量客户端,介绍如何在当前 FastAPI 仓库中通过重写 make_not_authenticated_error 方法,让 HTTPBearer 等安全类精确恢复 403 行为,同时说明其底层实现与测试验证路径。

背景:认证失败该返回 401 还是 403?

HTTP 规范对两者有明确分工:401 Unauthorized 表示"请求尚未经过身份验证,或未提供有效凭证",且响应必须携带 WWW-Authenticate 头以告知客户端如何发起挑战(challenge);而 403 Forbidden 表示"服务器已理解请求,但拒绝执行",通常用于已认证但无权限的场景。相关定义见 RFC 7235 与 RFC 9110 中关于 401 Unauthorized 的章节。

FastAPI 0.122.0 之前,内置安全工具在认证失败后向客户端返回 403 Forbidden。从 0.122.0 开始,它们改用更合适的 401 Unauthorized,并依据 HTTP 规范在响应中返回恰当的 WWW-Authenticate 头。

然而,如果客户端的既有逻辑依赖旧的 403 行为(例如前端或第三方集成把 403 当作"请跳转登录页"的硬编码判断),升级 FastAPI 后可能引发回归。此时不需要改动全局异常处理,只需要在自己的安全类中重写 make_not_authenticated_error 方法即可恢复旧行为。

迁移到 401 的源码依据

从源码结构看,这一改动是通过统一提取"构造未认证错误"的钩子方法实现的。所有内置安全类都在各自的基类中实现了 make_not_authenticated_error,并在检测到认证缺失或凭证非法时通过 raise 抛出它返回的异常实例:

  • fastapi/security/http.pyHTTPBase 中的默认实现返回 status_code=HTTP_401_UNAUTHORIZEDdetail="Not authenticated",并通过 make_authenticate_headers() 附带形如 WWW-Authenticate: Bearer 的头;
  • fastapi/security/api_key.pyAPIKeyBase 同样返回 401,由于 API Key 没有标准化的挑战值,实现中发送了自定义挑战 WWW-Authenticate: APIKey(其注释明确引用了 RFC 9110 对 401 必须携带该头的要求);
  • fastapi/security/oauth2.pyOAuth2 基类默认返回 401WWW-Authenticate: Bearer,注释说明 OAuth2 规范未定义固定挑战值,出于实用考虑采用最常见的 Bearer 挑战。

HTTPBasicHTTPBearerHTTPDigest__call__ 实现中,凡是凭证缺失、scheme 不匹配或解码失败的路径,都会调用 raise self.make_not_authenticated_error()(例如 fastapi/security/http.pyfastapi/security/http.py)。这正是该钩子方法能被安全子类覆盖以实现状态码定制的关键:无论认证过程有多少失败分支,最终生成的异常都出自同一个方法。

重写 make_not_authenticated_error 恢复 403

在 FastAPI 0.122.0+ 中,若需恢复旧版 403 Forbidden 行为,可在继承 HTTPBearer(或其他安全类)后重写该方法。官方指南 authentication-error-status-code 中给出完整示例,仓库中的可执行源码位于 docs_src/authentication_error_status_code/tutorial001_an_py310.py,全文如下:

from typing import Annotated

from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer

app = FastAPI()


class HTTPBearer403(HTTPBearer):
    def make_not_authenticated_error(self) -> HTTPException:
        return HTTPException(
            status_code=status.HTTP_403_FORBIDDEN, detail="Not authenticated"
        )


CredentialsDep = Annotated[HTTPAuthorizationCredentials, Depends(HTTPBearer403())]


@app.get("/me")
def read_me(credentials: CredentialsDep):
    return {"message": "You are authenticated", "token": credentials.credentials}

关键细节说明:

  • 子类 HTTPBearer403 仅重写了 make_not_authenticated_error,其余认证与 OpenAPI 集成逻辑完全继承自父类,因此 OpenAPI 文档中该安全方案(名为 HTTPBearer403)依然是标准的 http + bearer scheme;
  • 重写方法返回构造好的 HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not authenticated"),注意这里不再附带 WWW-Authenticate 头——这与 403 语义一致,因为只有 401 才被要求携带挑战头;
  • 可复用 status 模块中预定义常量,如 status.HTTP_403_FORBIDDEN,避免硬编码魔法数字;
  • CredentialsDep 通过 Annotated 类型别名将安全依赖与类型提示绑定,供路径操作函数直接使用。

官方文档特意强调一个易错点:该方法返回的是异常实例(return),而不是抛出它(raise)。真正把异常抛出的动作发生在 FastAPI 内部安全依赖的执行代码中,若在此处直接 raise,会导致异常提前离开安全类上下文的正常调用路径,破坏依赖注入流程。

运行验证与测试用例佐证

仓库为上述教程源码提供了端到端测试 tests/test_tutorial/test_authentication_error_status_code/test_tutorial001.py,可用它验证改造后的真实行为:

# 携带合法 Bearer 凭证访问 -> 200
response = client.get("/me", headers={"Authorization": "Bearer secrettoken"})
assert response.status_code == 200
assert response.json() == {
    "message": "You are authenticated",
    "token": "secrettoken",
}

# 不携带任何凭证访问 -> 403(即恢复后的旧行为)
response = client.get("/me")
assert response.status_code == 403
assert response.json() == {"detail": "Not authenticated"}

该测试同时校验了 /openapi.json:安全方案名沿用子类类名 HTTPBearer403,OpenAPI 声明为 {"type": "http", "scheme": "bearer"},与父类 HTTPBearer 一致——说明重写错误状态码不会破坏自动生成的接口文档。测试还通过 snapshot 固化了 OpenAPI schema,防止后续改动意外变更文档结构。

如何验证新旧行为的差异

若本地环境已升级到含该改动的 FastAPI 版本,可分别构造两次无凭证请求直观对比:

  • 默认 HTTPBearer:响应状态码为 401,响应体为 {"detail": "Not authenticated"},且响应头携带 WWW-Authenticate: Bearer
  • 重写后的 HTTPBearer403:响应状态码为 403,响应体同为 {"detail": "Not authenticated"},但不携带 WWW-Authenticate 头。

这解释了迁移的完整含义:不仅是状态码变化,还新增了符合规范的挑战头。旧客户端若只判断 403 而不解析 WWW-Authenticate,升级后必然受影响,此时按本文方案做最小化定制即可平滑过渡。

迁移注意事项

  1. 影响面make_not_authenticated_error 同时存在于 HTTP(Basic/Bearer/Digest)、API Key(Query/Header/Cookie)与 OAuth2(OAuth2PasswordBearerOAuth2AuthorizationCodeBearer 等)各类的基类中,本方案对所有内置安全类型通用,无需为每个子类重复实现;若项目使用自定义安全方案或非 Bearer 的 OAuth2 流程,可参考 fastapi/security/oauth2.py 中注释建议按需重写挑战值。
  2. 临时兼容而非长期方案403 用于"已识别但拒绝",401 用于"未认证",按规范语义应使用 401 并携带 WWW-Authenticate 引导客户端补全凭证。恢复 403 仅是存量客户端的过渡手段,建议同步推动客户端适配新行为。
  3. 保持 OpenAPI 一致性:重写只影响运行时错误响应,不影响 /openapi.json/docs 中的安全 scheme 声明(见上文 schema 断言),因此无文档漂移风险。

若需要同时支持"可选认证"(允许匿名访问时返回 None 而非报错),仍可配合安全类自带的 auto_error=False 参数使用;auto_error 控制失败分支走 raise 还是返回 None,而 make_not_authenticated_error 只负责定制真正报错时的异常内容,两者职责独立,可在 fastapi/security/http.pyHTTPBearer 实现中交叉印证。

相关参考文档见 docs/hi/docs/how-to/authentication-error-status-code.md(本文对应翻译)与官方英文版 authentication-error-status-code

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391