SQL参数校验不能只靠后端if判断,因前端可绕过、字符串拼接和ORM误用会使校验失效;应将校验下沉至数据契约层,用Pydantic等Schema工具在运行时强制约束类型、范围、枚举、空值及隐式转换,并统一处理错误提示。

SQL参数校验为什么不能只靠后端if判断
因为绕过前端、拼接字符串、ORM误用都会让if形同虚设——真正能守住边界的,是把校验逻辑下沉到数据契约层。Schema不是文档,是运行时可执行的约束声明。
常见错误现象:TypeError: expected string, got None、psycopg2.DataError: invalid input syntax for type integer,这类报错其实早该在参数进SQL前就拦截,而不是等数据库吐出错误再兜底。
- 使用场景:API接口接收
user_id、status、created_after等查询参数,需确保类型、范围、格式合法 - 不要在SQL拼接前做
int(param)强转——失败就炸,且无法统一处理错误提示 - Schema验证必须覆盖空值、类型隐式转换(如
"1"→1)、枚举白名单(如status只能是"active"/"inactive")
用Pydantic v2定义查询参数Schema最简写法
Pydantic是目前Python生态里对查询参数校验最自然的方案,不依赖FastAPI也能用,核心是把参数结构变成可验证的数据模型。
示例:一个带分页和状态过滤的用户查询
from pydantic import BaseModel, Field, field_validator
from typing import Optional, Literal
<p>class UserQuerySchema(BaseModel):
user_id: Optional[int] = Field(None, ge=1) # >=1,None允许为空
status: Optional[Literal["active", "inactive"]] = None
page: int = Field(1, ge=1)
size: int = Field(20, ge=1, le=100)</p><pre class="brush:php;toolbar:false;">@field_validator("user_id")
def validate_user_id(cls, v):
if v is not None and v = 1")
return v
-
Field直接声明约束(ge=greater than or equal),比写@validator更轻量 -
Literal强制枚举,传"pending"会直接报Input should be 'active' or 'inactive' - 别用
Optional[str]配default=None——Pydantic v2默认就允许None,重复写反而容易漏掉None校验分支 - 如果用Flask或纯WSGI,调用
UserQuerySchema.model_validate(request.args.to_dict())即可,失败抛ValidationError
PostgreSQL/MySQL字段类型和Schema怎么对齐
数据库字段定义(比如status VARCHAR(10))不等于校验规则。VARCHAR能存任意字符串,但业务上可能只接受两个值——Schema要补上这层语义。
-
id SERIAL→ Schema用int+ge=1,别信“数据库自增就安全”,恶意请求仍可传-999 -
created_at TIMESTAMPTZ→ 查询参数created_after建议用datetime类型,配合strptime解析ISO格式("2024-05-20T09:30:00Z"),别接受"2024/05/20"这种模糊格式 - MySQL的
TINYINT(1)常被当布尔用,但实际能存0~255——Schema必须用Literal[0, 1]或bool并指定coerce行为,否则2会静默入库 - 注意时区:数据库存UTC,但用户传
"2024-05-20"默认是本地时区,Schema层就要决定是转UTC还是拒绝非ISO带时区格式
校验失败后怎么返回友好错误而不暴露SQL细节
直接把Pydantic的ValidationError堆栈打给前端,等于告诉攻击者你用了什么库、字段名是什么、甚至表结构。必须重写错误输出。
- 捕获
ValidationError后,用exc.errors()提取字段+错误类型,映射成业务语言:{"user_id": "必须是正整数"},而非"Input should be greater than or equal to 1" - 不要在错误消息里出现
SQL、query、column、table等词,避免泄露数据模型 - 对
422 Unprocessable Entity响应体,只包含field、message、code三个键,其他一概不加——前端按这个契约渲染,后端不额外扩展 - 日志里可以记完整
exc.json(),但响应体必须脱敏。这点容易被忽略:开发时图省事直接return str(exc),上线就成漏洞
事情说清了就结束










