必须先校验SQL合法性再限频:先做长度检查和语法解析(如sqlparse),仅合法SQL才进入Redis令牌桶限频,否则非法请求不占配额、不记日志,防止误限与绕过。

SQL 输入合法性校验:先过滤再解析,别直接扔给数据库
用户输入的 SQL 字符串不能直接进 execute(),哪怕加了参数化。常见错误是只做关键词黑名单(如拦截 drop、union),结果被绕过——比如大小写混写、注释分割、Unicode 空格、内联换行等都能逃逸。
真正有效的做法是走轻量语法解析,而非正则匹配:
- 用
sqlparse(Python)或pg-escape(Node.js)做结构化解析,检查顶层语句类型是否仅限SELECT/INSERT/UPDATE(按需配置) - 拒绝含子查询中嵌套 DDL、带
WITH RECURSIVE且深度超 2、存在未闭合括号或引号的语句 - 对表名、列名做白名单校验(比如只允许匹配
^[a-zA-Z_][a-zA-Z0-9_]{0,63}$),不依赖上下文推断 - 注意:PostgreSQL 的
EXPLAIN (FORMAT JSON)可用于无副作用验证语法,但会消耗少量解析资源,高并发下慎用
令牌桶限频:用 Redis 原子操作实现毫秒级精度
用内存计数器或本地缓存做限频,在分布式环境必然失效;用数据库更新再查,性能扛不住。正确路径是靠 Redis 的 INCR + EXPIRE 组合模拟令牌桶,但必须处理好重置逻辑。
关键细节:
- 桶容量和填充速率要按业务设定,比如每秒 5 次 → 容量设为 5,
rate=5,时间窗口用EXPIRE设为 1 秒 - 每次请求先
INCRkey,再EXPIRE一次(防止 key 不存在时漏设过期) - 若返回值 > 容量,说明已超限,立刻拒绝并返回
429 Too Many Requests - 注意:Redis 6.2+ 支持
INCRBYFLOAT和 Lua 脚本做更精确的令牌填充,但多数场景用整数桶更稳
SQL 校验与限频的组合顺序:必须先校验再限频
如果先限频再校验,恶意用户可用非法 SQL(如超长字符串、畸形语法)持续触发限频逻辑,导致正常用户被误限。真实链路应该是:
- 收到请求后,第一步:快速做基础长度检查(如
len(sql) > 10240直接拒) - 第二步:调用 SQL 解析器判断是否合法可执行(耗时约 1–5ms)
- 第三步:仅当合法时,才进入 Redis 限频流程
- 否则,非法请求不占配额,也不记日志(防探测放大)
这个顺序一旦颠倒,就等于把防御门槛从“语法层”降到了“网络层”,后面所有策略都容易被绕过。
容易被忽略的边界:空格、BOM、隐藏控制字符
用户粘贴的 SQL 经常带不可见字符:UTF-8 BOM(\ufeff)、零宽空格(\u200b)、CRLF 混用,这些会导致 sqlparse 解析失败或误判语句类型。
预处理必须做:
- 用
sql.strip().replace('\uFEFF', '').replace('\u200B', '')清除常见 BOM 和零宽字符 - 统一换行符为
\n(避免 Windows 下\r\n干扰解析器识别) - 拒绝含非打印 ASCII(
0x00–0x08, 0x0B–0x0C, 0x0E–0x1F)的输入,这类基本不是人工写的 SQL - 注意:某些 ORM(如 SQLAlchemy)在 debug 模式下会自动 trim,但生产环境不会,务必自己做
没清理这些,再严的语法校验也会在第一步就崩掉。

















