先纠正一个常见误解:Token未校验本身并不会直接导致SQL注入——它只是一个身份凭证,不是SQL的输入源。真正的问题出在另外一条链路上:攻击者利用未校验的Token轻松绕过鉴权,然后塞进恶意参数(比如 user_id、keyword),这些参数再被拼接到SQL中执行,这才是灾难的开端。

所以修复的重点从来不是Token本身,而是整条攻击链上最脆弱的一环:SQL拼接。鉴权只能解决“谁在调用”,解决不了“传了什么参数”。下面我们展开来说。
为什么只校验Token无法防SQL注入
举个最简单的例子:即使Token合法且未过期,只要后端代码里写着 "WHERE id = '" + userId + "'" 这种拼接,攻击者照样可以构造 userId=1' OR '1'='1 来突破数据库防护。这就好比,门禁卡没问题,但进门后直接把危险物品搬进了保险库。鉴权只是第一道门,SQL层才是真正的防线。
根治措施:禁用所有字符串拼接构造SQL的写法
这是唯一能根除漏洞的手段。无论Token校验得有多严、有效期有多短,只要SQL构建层强制使用了参数化查询,恶意输入就永远只是数据,永远不会变成可执行的代码。具体到不同技术栈:
- Ja va:强制用
PreparedStatement,占位符统一为?,调用setString()或setLong()。绝对不要用String.format()或+拼接。 - Python(MySQL/PostgreSQL):用
cursor.execute("SELECT * FROM t WHERE id = %s", [user_id]);SQLite 用?占位符。严禁写f"WHERE name = '{name}'"这种代码。 - Node.js(pg):用
client.query("SELECT * FROM u WHERE id = $1", [id]);mysql2同理 —— 用query(sql, [params]),别碰模板字符串。 - MyBatis:确认只使用
#{}(参数化),禁用${}(字符串拼接)。检查所有标签和动态 SQL 是否引入了外部变量。
网关层对Token的校验要绑定业务参数初筛
网关当然不能替后端防注入,但可以配合Token鉴权做轻量级兜底,挡住明显畸形的请求:
- Token校验必须包含时效性:
timestamp参数必须存在,且与当前时间差 ≤ 300000ms(5分钟),超时直接拒绝。 - 必须校验
nonce去重:长度 ≥ 8,并在Redis缓存最近5分钟内已经见过的nonce,重复立即拒绝。 - 对已通过Token验证的请求,额外对高危字段做初筛:
user_id用正则^[0-9]+$;out_trade_no用^[a-zA-Z0-9_-]{8,64}$;keyword限制长度 ≤ 200。 - 拒绝含
%00、连续%%、未闭合%2的URL编码——这些不是正常客户端该有的行为。
修复后必须验证是否真生效
别信“加了Token校验就安全了”。上线前务必用真实payload测试:
- 发送
GET /api/order?order_id=123%27%20UNION%20SELECT%20password%20FROM%20users--,观察返回的是400或500错误,而不是用户数据。 - 用
sqlmap -u "https://api.example.com/order?order_id=123" --batch扫描,确认无注入点。 - 检查日志:如果某次请求Token有效但参数含
' OR '1'='1,应该记录原始参数并触发告警——这说明密钥可能泄露,或客户端已经失陷。
真正危险的,不是没有校验Token,而是校验完Token就以为万事大吉,然后在DAO层堂而皇之地写着 String sql = "SELECT * FROM user WHERE id = " + id;。这种代码,再强的网关也拦不住。
