前端用 Prepared Statement 能防 SQL 注入吗?

东方晓萌 阅读 31

我最近在学安全防护,看到说用 Prepared Statement 可以防止 SQL 注入。但我是在写前端代码(比如用 fetch 发请求),那我在前端拼 SQL 字符串然后发给后端,是不是照样会被注入?

比如我这样写:const query = SELECT * FROM users WHERE id = ${userId};,就算后端用了 PreparedStatement,前端这么拼接是不是还是有风险?

是不是必须让后端自己处理参数绑定,前端只传参数不能传整条 SQL?

我来解答 赞 11 收藏
二维码
手机扫码查看
2 条解答
慧研(打工版)
这个问题问得挺好的,说明你在思考安全防护的细节。

先说结论:你说对了,如果前端直接拼接完整的SQL语句发给后端,后端就算用了PreparedStatement也白搭。

为什么无效?

PreparedStatement的原理是“先编译SQL结构,再绑定参数”。它的防护机制是这样的:数据库先解析SQL语句的结构(这一步确定了哪些是SQL指令、哪些是占位符),然后你绑定进去的参数永远被当作数据处理,不会再被解析成SQL代码。

但问题在于,你前端已经把整条SQL拼好了发过去,后端拿到的是什么?是 SELECT * FROM users WHERE id = 1 OR 1=1 这么一条完整的SQL字符串。后端顶多帮你执行这条SQL,PreparedStatement在这种情况下根本没用武之地——它收到的已经是"成品"了,根本没有机会区分哪部分是结构、哪部分是数据。攻击者在前端把 1 OR 1=1 往上一填,整条SQL就变成恶意指令了。

正确的做法

前端只传参数,后端自己拼SQL结构:

// 前端 - 就传个id参数,别整那些有的没的
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId: 123 })
});


// 后端 Node.js/Express + 伪代码示例
app.post('/api/users', async (req, res) => {
const { userId } = req.body;

// 后端自己写SQL模板,用占位符
const sql = 'SELECT * FROM users WHERE id = ?';

// 用PreparedStatement执行,参数绑定进去
const result = await db.execute(sql, [userId]);

res.json(result);
});


这样用户传过来的 123 就只是个值,数据库不会把它当成SQL指令的一部分去解析。

总结一下

SQL注入是后端数据库层面的问题,跟前端怎么拼HTTP请求没关系。前端代码根本接触不到数据库,它只是按照后端的API规范传数据而已。真正的防护必须在后端完成——后端用PreparedStatement绑定参数,前端别掺和SQL的构建。

你要是实在不放心,可以看看后端日志,看看收到的请求内容是什么样的。你会看到前端发过来的就是纯JSON数据,SQL影子都看不到,这就对了。
点赞
2026-03-19 19:12
Dev · 甜茜
你这个问题问得很到位,说明你已经开始思考安全边界的问题了,这比很多人强多了。

先说结论:前端拼 SQL 字符串,哪怕后端用了 PreparedStatement,只要前端能传完整的 SQL 片段或语句,就依然有注入风险。

原理是这样:PreparedStatement 防注入的关键在于「参数和语句分离」,数据库在预编译阶段就把 SQL 结构固定死了,后面传进来的参数只当作纯数据处理,不会被当成 SQL 语法解析。但前提是:SQL 结构必须由后端完全控制,参数只作为占位符传入。

你举的例子:

const query = SELECT * FROM users WHERE id = ${userId};


如果这段代码在前端执行,然后把整个 query 字符串通过 fetch 发给后端,后端再直接执行(哪怕用了 PreparedStatement,但如果你是把整个 query 字符串当作一个参数传进去,比如 stmt = conn.prepareStatement(query) 这种用法),那 PreparedStatement 就形同虚设了——因为 SQL 结构已经在前端被拼死了,攻击者可以构造像 userId = "1 OR 1=1" 或者更狠的 userId = "1; DROP TABLE users; --" 这样的输入,直接把恶意 SQL 注进去。

举个极端点的例子:假设你前端这么发请求:

const userId = "1; DELETE FROM users; --";
const query = SELECT * FROM users WHERE id = ${userId};
fetch('/api/getUser', {
method: 'POST',
body: JSON.stringify({ sql: query })
});


后端如果这样处理:

// 假设是 Node.js + mysql2
const sql = req.body.sql;
const [rows] = await connection.execute(sql); // 直接执行字符串,不是预编译!


那完蛋,DELETE 语句真就执行了。

再退一步,就算后端用了 PreparedStatement,但你传的是整个 query 字符串作为参数:

const sql = req.body.sql; // 比如是 "SELECT * FROM users WHERE id = ?"
const stmt = await connection.prepare(sql); // 这里 prepare 的是整个字符串
await stmt.execute([userId]); // userId 是参数


这看起来像用了 PreparedStatement,但问题在于:SQL 语句结构本身是前端决定的,攻击者可以传 "SELECT * FROM users WHERE id = ? OR 1=1",然后参数传个 "1",结果还是被绕过了。

所以正确的做法是:

前端只传参数,别传 SQL 结构;后端固定 SQL 模板,用占位符绑定参数。

比如后端写死这个 SQL:

SELECT * FROM users WHERE id = ?


前端只发:

{ "userId": "123" }


后端收到后,用这个固定模板去预编译,把 userId 当作参数传进去。这样哪怕 userId 是 "1 OR 1=1",它也只会被当成字符串值去比对,不会被当 SQL 执行。

用代码演示一下正确姿势:

前端(比如 React / Vue / 原生 JS 都一样):

const userId = document.getElementById('input').value; // 用户输入的值,比如 "123"
fetch('/api/user', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: userId }) // 只传参数,不拼 SQL
});


后端(Node.js + mysql2 为例):

app.post('/api/user', async (req, res) => {
const { id } = req.body;

// 后端固定 SQL 模板,用 ? 占位符
const sql = 'SELECT * FROM users WHERE id = ?';

// 用预编译 + 参数绑定,确保 id 被当作数据处理
const [rows] = await connection.execute(sql, [id]);

res.json(rows);
});


这样不管前端传什么,id 都不会被当作 SQL 语法解析。哪怕有人传 id = "1 OR 1=1",数据库执行的其实是:

SELECT * FROM users WHERE id = '1 OR 1=1'


它只是查 id 字段等于这个字符串的记录,根本不会触发 OR 逻辑判断。

再强调一遍:SQL 预编译只在后端生效,前端永远不该知道数据库结构,更不该参与 SQL 拼接。前端只负责传「数据」,后端负责「语句」。

顺便吐槽一句:很多初学者以为用了 ORM 就万事大吉,但其实如果 ORM 的 query 构造也是前端传字符串,或者用了 raw() / exec() 这类动态执行方法,一样有风险。安全这事,得从最底层的通信协议设计开始就分清边界。

最后再补一句:如果你的后端 API 是给前端调用的,那它就该是「受保护的内部接口」,不是「给用户直接写 SQL 的入口」。真要搞复杂查询,也该由后端暴露「带参数的查询接口」,比如 /api/users?role=admin&status=active,后端解析这些参数去构造 SQL,而不是让用户传 SQL 字符串回来。

明白了吗?要是还有疑问,比如用 Java / PHP / Python 的例子,我可以再给你补几个语言的代码。
点赞 1
2026-02-27 05:00