首页 / FastAPI 入门教程 / API Key 与 HTTP Basic 认证

FastAPI 入门教程

API Key 与 HTTP Basic 认证

本教程共 50 篇 · 第 31 篇 · 更新于 2026-08-12 · 约 8 分钟阅读

FastAPIFastAPI 入门教程API KeyHTTP Basic认证方式安全

本节目标:掌握两种更简单的认证方式——API Key(固定密钥)和 HTTP Basic(浏览器原生账号密码),知道它们适合什么场景,以及和前面学的 OAuth2 密码流怎么选。

31-1 不是所有接口都要 OAuth2

前面三章讲的 OAuth2 密码流 + JWT,适合”真人用账号密码登录”的网页或 App。但现实里还有不少场景没那么复杂:

  • 两个程序之间互相调用,没有”用户”概念,只想用一个固定密钥。
  • 内部运维接口,图省事,浏览器弹个框输账号密码就行。

这些时候,上全套 OAuth2 反而重了。FastAPI 在 fastapi.security 里还提供了更轻的方案:API KeyHTTP Basic。本章就把它们讲清楚。

Note

这些方案全都集成进 OpenAPI 文档。用了它们,文档里会自动出现对应的认证按钮,和 OAuth2 一样方便调试。

31-2 API Key 是什么

API Key(接口密钥)是最朴素的认证:服务端发给你一串固定字符串,你每次请求带着它,服务端一比对就知道”是你”。

它通常放在三个位置之一:

  • 请求头(header):最推荐,比如 X-API-Key: abc123
  • 查询参数(query):拼在 URL 后面 ?api_key=abc123
  • Cookie:放 Cookie 里

FastAPI 用 APIKeyHeaderAPIKeyQueryAPIKeyCookie 分别对应这三种。它们都来自 fastapi.security

31-3 用 APIKeyHeader 校验请求头密钥

最常见的是放请求头。写法和一个安全方案类很像:

from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import APIKeyHeader

app = FastAPI()

api_key_header = APIKeyHeader(name="X-API-Key")


async def get_api_key(api_key: str = Depends(api_key_header)):
    if api_key != "secret-api-key":
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="无效的 API Key",
        )
    return api_key


@app.get("/secure/")
async def secure_endpoint(api_key: str = Depends(get_api_key)):
    return {"message": "密钥正确,欢迎访问"}

这里 APIKeyHeader(name="X-API-Key") 声明:从名为 X-API-Key 的请求头取密钥。取不到,FastAPI 自动回 403。取到了,交给 get_api_key 做比对。

Tip

真实项目里别用 == 写死字符串比对,也别把密钥硬编码在代码里。应从环境变量读,并用恒定时间比较(见下文 Basic 部分的 secrets.compare_digest)。

31-4 用 APIKeyQuery 放在 URL 参数

如果客户端不方便设请求头,可放查询参数。把 APIKeyHeader 换成 APIKeyQuery

from fastapi.security import APIKeyQuery

api_key_query = APIKeyQuery(name="api_key")


async def get_api_key(api_key: str = Depends(api_key_query)):
    if api_key != "secret-api-key":
        raise HTTPException(status_code=403, detail="无效的 API Key")
    return api_key


@app.get("/secure/")
async def secure_endpoint(api_key: str = Depends(get_api_key)):
    return {"message": "通过 URL 参数认证成功"}

调用时就是 GET /secure/?api_key=secret-api-key 这样。能放请求头就别放 URL,因为密钥拼在 URL 里会留在浏览器历史和服务器日志中。

Note

API Key 本质是”谁有这串字符谁就能用”,无法区分具体用户,也不带过期时间。它适合服务对服务、或临时调试,不适合做正式的用户登录。

31-5 HTTP Basic 是什么

HTTP Basic Auth 是 HTTP 协议自带的认证方式。它的流程很”原始”:

  • 客户端第一次访问没带凭据,服务端回 401,并在 WWW-Authenticate 头写 Basic
  • 浏览器看到 Basic,自动弹出原生的账号密码输入框。
  • 用户输完,浏览器把 账号:密码 用 base64 编码后放进 Authorization: Basic xxxx 头,自动重发。
  • 服务端解码比对,对了就放行。

它不需要你写登录页面,浏览器帮你搞定交互。适合内部工具、运维后台这类”自己人用”的场景。

31-6 用 HTTPBasic 收账号密码

FastAPI 提供 HTTPBasicHTTPBasicCredentials。前者声明方案,后者在依赖里拿到解码后的 usernamepassword

from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials

app = FastAPI()
security = HTTPBasic()


async def get_current_user(credentials: HTTPBasicCredentials = Depends(security)):
    if credentials.username == "stanley" and credentials.password == "swordfish":
        return credentials.username
    raise HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="账号或密码错误",
        headers={"WWW-Authenticate": "Basic"},
    )


@app.get("/basic/")
async def basic_endpoint(username: str = Depends(get_current_user)):
    return {"user": username}

没带凭据时,HTTPBasic 自动回 401 并带 WWW-Authenticate: Basic,浏览器弹框。带了且对了,credentials.username / credentials.password 就是解码后的明文。

31-7 防时序攻击:用 secrets.compare_digest

上面那句 == 直接比,有个隐患叫时序攻击(timing attack)。Python 比字符串时,一旦发现第一个字符不对就立刻返回 False。攻击者能靠”服务器响应慢了几微秒”猜出前面几个字母对了,逐字爆破。

正确做法是用标准库 secrets.compare_digest,它比较时耗时恒定,不泄露信息:

import secrets

async def get_current_user(credentials: HTTPBasicCredentials = Depends(security)):
    correct_user = secrets.compare_digest(credentials.username, "stanley")
    correct_pass = secrets.compare_digest(credentials.password, "swordfish")
    if not (correct_user and correct_pass):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="账号或密码错误",
            headers={"WWW-Authenticate": "Basic"},
        )
    return credentials.username

注意 secrets.compare_digest 要求传入 str 或纯 ASCII 的 bytes。遇到中文用户名要先 encode("utf-8") 转字节再比。

Note

时序攻击听起来玄乎,但这是实打实的安全风险。凡是比”密码/密钥”这类秘密,都用 secrets.compare_digest,养成习惯。前面 API Key 的比对同理。

31-8 三种方式怎么选

把本章两个和前面 OAuth2 放一起,给你一张速查表:

  • OAuth2 密码流 + JWT:适合真人登录的网页/App。有令牌、有过期、能带用户信息,最完整。前面 26–30 章全讲它。
  • API Key:适合服务对服务、脚本调用、临时调试。简单,但无用户概念、无过期,密钥要保管好。
  • HTTP Basic:适合内部工具、运维页。浏览器原生弹框,零前端代码,但每次传账号密码(靠 HTTPS 保护),不适合正式用户体系。
Tip

没有”最好”的认证,只有”最合适”的。对外产品用 OAuth2;内部脚本用 API Key;自己调试的后台用 HTTP Basic。它们都能在 FastAPI 里几行搞定,并且都进 OpenAPI 文档。

31-9 一个 API Key 完整示例

把请求头版 API Key 写成可直接跑的 main.py

from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import APIKeyHeader

app = FastAPI()

api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)


async def get_api_key(api_key: str = Depends(api_key_header)):
    # 真实环境应从环境变量读正确密钥,并用 secrets.compare_digest 比对
    if api_key != "secret-api-key":
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="无效的 API Key",
        )
    return api_key


@app.get("/secure/")
async def secure_endpoint(api_key: str = Depends(get_api_key)):
    return {"message": "密钥正确,欢迎访问"}

启动后这样调:

curl "http://127.0.0.1:8000/secure/" -H "X-API-Key: secret-api-key"

不带或带错密钥会收到 403。把 auto_error=False 去掉,FastAPI 会在缺头时直接 403,更省事。

31-10 小结

至此,安全这一组讲完了。回顾一下地图:

  • 第 26 章建立语言:认证、授权、OAuth2、JWT。
  • 第 27 章密码哈希,杜绝明文。
  • 第 28 章登录发 JWT。
  • 第 29 章用 OAuth2PasswordBearer 保护接口。
  • 第 30 章用 scopes 做角色权限。
  • 本章补上 API Key 与 HTTP Basic 两种轻量方案。

认证这件事,核心就一句话:选对方案,验证凭据,再决定放不放行。FastAPI 把这些方案的规范细节都包成了几行代码,你只管写业务逻辑。