Pydantic 不防 SQL 注入,仅做数据校验;防注入靠参数化查询。需显式加字段约束、白名单校验、路径参数手动检查,并杜绝字符串拼接SQL。

Pydantic 本身不防 SQL 注入,它只做数据结构校验;真正起作用的是你后续用 SQLAlchemy 或其他 ORM 执行参数化查询的方式。把 Pydantic 当成“安检门”,它拦不住伪装成合法字符串的 ' OR 1=1,但能帮你筛掉明显错类型、超长、缺失字段的数据,为参数化查询铺好路。
Pydantic 字段约束必须显式写,不能靠 str 默认行为
声明 username: str 不代表安全——攻击者传 ' OR '1'='1 完全符合该类型。你得主动加约束:
- 用
Field(min_length=2, max_length=50)控制长度,避免超长 payload 触发缓冲区异常或 WAF 误杀 - 对用户名、路径片段等结构化字段,用
pattern=r'^[a-zA-Z0-9_]+$'白名单限制(但注意:邮箱、中文名等业务字段不能这么干) - 数值类字段优先用语义类型,比如
page: conint(gt=0, le=1000),防止OFFSET 999999999拖垮数据库 - 搜索字段(如
q: str)配合Query(strip_whitespace=True)和截断逻辑,比如q[:200]
参数化查询才是防注入的唯一防线
Pydantic 校验完的数据,如果最后还是拼进 SQL 字符串,那前面所有校验都白费。关键在执行层:
FastAPI + Flask 混合部署最佳实践,解决路由定义、API 代理等常见问题,适用于同时运行 FastAPI API 与 Flask 前端的场景。
- 绝对禁止:
f"SELECT * FROM users WHERE email = '{user.email}'"或.format()拼接 - 正确做法:
session.execute(select(User).where(User.email == user.email))—— ORM 内部自动参数化 - 用
text()写原生 SQL 时,必须用命名参数:text("SELECT * FROM users WHERE status = :status"),再传{"status": user.status} - 动态表名/列名无法参数化,必须白名单硬校验:
if table_name not in ["users", "orders"]:后面直接 raise
IN 子句和嵌套结构是高频翻车点
Pydantic 的 List[int] 或 List[str] 不能直接塞进 WHERE id IN (?),SQLAlchemy 不会自动展开占位符:
- 错误:
.filter(User.id.in_(user_ids))在某些旧版本或 raw query 场景下可能退化为字符串拼接 - 稳妥写法:
where(User.id.in_(bindparam("ids", expanding=True))),或手动构造等长占位符:"WHERE id IN ({','.join([':id'] * len(ids))})"+{"id": ids} - 字典/列表存数据库时(如 JSON 字段),必须先用
json.dumps()转成字符串,Pydantic 不会替你做这步“翻译”
最常被忽略的盲点:路径参数(如 /user/{id})根本不会过 Pydantic 模型校验,FastAPI 直接当字符串交给路由函数——这里的数据若不经类型转换或白名单检查,就直接进了查询,风险极高。

















