模板字面量本身不防sql注入,安全依赖标签函数将用户输入转为独立参数数组并交由数据库驱动参数化执行,而非拼入sql字符串。

模板字面量本身不防 SQL 注入,真正起作用的是你写的标签函数——它必须把用户输入从 SQL 字符串中彻底剥离,交给数据库驱动做参数化执行。
标签函数是关键,不是语法糖
很多人以为写了 sql`SELECT * FROM users WHERE id = ${id}` 就安全了,其实完全错误。模板字符串只是把 ${id} 转成字符串再拼进去,和 "SELECT ... " + id 一样危险。
真正安全的前提是:标签函数要拦截原始字符串片段(parts)和插值参数(values),然后只生成带 ? 占位符的语句 + 独立参数数组,绝不把任何用户数据拼进 SQL 字符串里。
- 函数名(如
sql)只是约定,没有语法强制力;安全与否全看函数内部是否坚持参数分离 - 若函数内部调用了
String(value)再拼进字符串,立刻失效 - 生成的
{ text: "... ? ...", params: [...] }必须传给支持预处理的驱动(如pg、mysql2),不能自己用eval或字符串拼接去“执行”
一个可用的 sql 标签函数示例
下面这个函数不执行查询,只做结构转换:
function sql(parts, ...values) {
let query = parts[0];
const params = [values[0]];
for (let i = 1; i <p>使用时:</p><pre class="brush:php;toolbar:false;">const id = "1; DROP TABLE users; --";
const active = true;
const result = sql`SELECT * FROM users WHERE id = ${id} AND active = ${active}`;
// → { text: "SELECT * FROM users WHERE id = ? AND active = ?", params: ["1; DROP TABLE users; --", true] }后续必须交由驱动执行,例如:await pg.query(result.text, result.params)。
TypeScript 模板字面量类型可增强校验
光靠运行时标签函数还不够。配合 TypeScript 的模板字面量类型,可以在编译期约束表名、字段名和参数类型:
- 定义合法表名为联合字面量:
type Table = 'users' | 'orders' | 'products' - 用模板类型限制
SELECT后只能出现该表真实字段:type UserField = 'id' | 'email' | 'password' - 结合泛型和条件类型,确保
WHERE name = ${x}中的x类型与name字段一致(比如string)
这样既防注入,又防拼错字段、类型不匹配等低级错误。
常见陷阱必须避开
即使用了标签函数,以下操作仍会导致防护失效:
- 在标签函数里对参数手动加引号或转义后拼进字符串(如
"'" + value + "'") - 把
result.text当普通 SQL 执行,绕过驱动的参数绑定机制 - 误以为 HTML 场景下的
html`<div>${x}</div>`可以复用于 SQL,二者上下文完全不同,不可混用 - 信任前端传来的“已处理”字符串,未在服务端重新走标签函数流程










