客户端验证拦不住SQL注入,因为JavaScript校验可被禁用脚本、curl直发、篡改前端或抓包重放等方式绕过,攻击者无需访问网页即可发送恶意payload;服务端才是唯一可信的入口守门人,必须对所有输入源进行白名单校验、危险字符过滤或转义,并配合参数化查询兜底防护。
必须放在服务端,客户端验证只能当辅助,不能当防线。
浏览器里的 JavaScript 验证可以被轻易绕过:禁用脚本、用 curl 直接发请求、改前端代码、抓包重放——所有这些操作都不经过你的 JS 校验逻辑。攻击者根本不需要打开网页,就能把 ' OR 1=1 -- 这类 payload 发到后端接口。
HTML5 的 type="email" 或 pattern 属性、Vue 的表单规则、React 的 onChange 校验,全属于客户端行为,不具安全约束力服务端验证不是“再检查一遍”,而是“唯一可信的入口守门人”。它要覆盖所有输入源:GET 查询参数、POST 表单、JSON 请求体、Cookie、HTTP 头(比如 X-Forwarded-For)、甚至 gRPC 或 WebSocket 消息。
[a-zA-Z0-9_]{3,20},ID 强制转成 int 类型'、"、;、--、/*、UNION、SELECT 等(注意:过滤不能替代参数化查询)400 Bad Request),绝不能泄露字段名、校验规则或数据库结构很多开发者误以为用了 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、超长邮箱),参数化查询兜底处理所有漏网之鱼。少一个,防护链就断了。