SQL注入防护:输入验证客户端还是服务端?
作者:技术拾光时间:2026-07-21 08:33:08浏览:455
SQL注入防护的核心在于服务端验证,客户端验证不可信赖。服务端需对所有输入源进行白名单校验与危险字符过滤,并配合参数化查询。验证失败应中断请求并返回通用错误,确保防护链完整。
你可能在想,在浏览器里用 JavaScript 做输入校验,能不能直接挡住 SQL 注入?答案是:不能。客户端验证只能作为辅助手段,绝不可当作防线——真正的守门人,必须放在服务端。

必须放在服务端,客户端验证只能当辅助,不能当防线。
为什么客户端验证拦不住 SQL 注入
原因很简单:浏览器里的 JavaScript 校验,本质上不可信任。攻击者可以禁用脚本,使用 curl 直接发送请求,修改前端代码,甚至抓包重放——所有这些操作,都不经过你的 JS 校验逻辑。换句话说,攻击者根本不需要打开你的网页,就能把 ' OR 1=1 -- 这样的 payload 直接送到后端接口。
- 用户提交的任何数据,只要没经过服务端处理,就等同于“未验证”。
- HTML5 的
type="email"或pattern属性、Vue 的表单规则、React 的onChange校验——这些全是客户端行为,不具备安全约束力。 - 哪怕你用 WebAssembly 或加密校验,只要验证逻辑跑在浏览器里,就存在被逆向和跳过的可能。
服务端验证该怎么做才有效
服务端验证不是“再检查一遍”,而是“唯一可信的入口守门人”。它需要覆盖所有输入源:GET 查询参数、POST 表单、JSON 请求体、Cookie、HTTP 头(比如 X-Forwarded-For),甚至 gRPC 或 WebSocket 消息。
- 优先使用白名单:比如用户名只允许
[a-zA-Z0-9_]{3,20},ID 强制转为int类型。 - 对不可预知格式的字段(如搜索关键词),至少过滤或转义危险字符:
'、"、;、--、/*、UNION、SELECT等。注意:过滤不能替代参数化查询。 - 验证失败必须立即中断请求,返回通用错误(如
400 Bad Request),绝不能泄露字段名、校验规则或数据库结构。
常见错误:把验证逻辑混进 ORM 或框架默认行为里
很多开发者误以为使用了 ORM(如 Django ORM、Sequelize、MyBatis)就能自动防注入——其实不然。ORM 的安全前提是:你没有手动拼接 SQL 字符串。
- MyBatis 中写
${username}是拼接,危险;用#{username}才是预编译,安全。 - Django 的
filter(name__icontains=request.GET.get('q'))是安全的,但raw()或extra()里拼接字符串就直接破防。 - SQLAlchemy 的
text()如果插入了用户输入,照样中招;必须用bindparam或execute(..., {'val': user_input})。
真正容易被忽略的是:验证和参数化查询不是二选一,而是必须同时存在。验证负责提前拦截明显非法输入(比如负数 ID、超长邮箱),参数化查询兜底处理所有漏网之鱼。少一个,防护链就断了。
