前端校验对SQL注入完全无效,因用户可禁用JS、用curl直发请求、在DevTools修改字段等绕过所有限制;真正防护必须在Node.js层对所有输入(params/query/headers等)实施参数化查询或白名单校验。

前端提交的数据本身不需要、也不应该承担防 SQL 注入的责任——校验必须落在后端,且不能只做一次。
为什么前端校验对 SQL 注入完全无效
用户禁用 JavaScript、用 curl 直发请求、在 DevTools 里改表单字段、甚至写个 Python 脚本批量 POST,都能绕过所有前端限制。HTML 的 type="email"、pattern、maxlength 或 Vue 的 v-model + @submit.prevent 校验,只影响用户体验,不改变请求本质。
真正危险的不是“用户输什么”,而是后端怎么用它:req.body.email、req.query.id、req.headers['x-api-key'] —— 只要这些值被拼进 SQL 字符串,就已高危。
二次校验必须发生在 SQL 构造前,而非入库时
所谓“二次校验”,是指:数据从数据库读出后,若将用于构造新 SQL(比如动态表名、ORDER BY 字段、WHERE 条件值),就必须重新验证——因为第一次入库时的参数化,不保护这次读出再拼接的行为。
立即学习“前端免费学习笔记(深入)”;
- 错误做法:
SELECT * FROM {$table} WHERE status = ?中的$table来自 DB 字段,未白名单校验 - 正确做法:提前定义允许的表名列表,如
const allowedTables = ['users', 'orders']; if (!allowedTables.includes(tableFromDb)) throw new Error('Invalid table') - 排序方向(
req.query.sortBy)也不能直接进ORDER BY ?—— 占位符不支持列名,必须白名单匹配:if (!['name', 'created_at', 'score'].includes(sortBy)) sortBy = 'created_at'
Node.js 中参数化查询的实操要点
用 mysql2、pg 或 sqlite3 时,唯一安全路径是占位符 + 数组/对象传参,绝不可字符串拼接。
- ✅ 安全:
pool.execute('SELECT * FROM users WHERE id = ? AND role = ?', [id, role]) - ✅ 安全(PostgreSQL):
client.query('SELECT * FROM logs WHERE level = $1 AND ts > $2', ['error', sinceDate]) - ❌ 危险:
pool.query(`SELECT * FROM ${tableName} WHERE ...`)—— 表名无法占位,必须白名单 - ❌ 危险:
sequelize.query("UPDATE t SET x = '" + userInput + "'")—— 即便用了 Sequelize,手写 raw 查询仍会破防
ORM 如 TypeORM 或 Sequelize 的模型方法(如 find、update)默认参数化,但一旦调用 query()、whereRaw()、orderByRaw(),就得自己扛起校验责任。
最容易被忽略的校验点
开发者常盯着 req.body,却漏掉其他入口:
-
req.params.id(URL 路径参数):如/user/:id中的:id,必须校验是否为数字或 UUID 格式 -
req.query.sortBy、req.query.limit:用于ORDER BY或LIMIT时,不能直接插进 SQL -
req.headers['x-tenant-id']:多租户场景下,若用于切换 schema 或表前缀,必须严格白名单 - 从 Redis / 文件 / 第三方 API 读取的配置值:只要它参与 SQL 拼接,就等同于用户输入
真正的防护粒度,是“每个变量进 SQL 前那一行代码”——而不是“这个接口有没有校验”。没有银弹,只有每次构造查询时,亲手确认每个动态部分是否可信。


















