preparedstatement能彻底防止sql注入,因其在jdbc协议层实现“先编译、后填参”,用户输入经二进制参数通道传入,绝不参与sql解析;但表名、列名等非参数位置仍需白名单校验。

只要用对 PreparedStatement,SQL 注入就不可能发生——它不是“大概率防住”,而是从 JDBC 协议层就切断了数据被当作代码执行的路径。
必须用 PreparedStatement,而不是 Statement + 字符串拼接
Statement 执行的是完整 SQL 文本,任何用户输入一旦拼进去,就可能改变语义。PreparedStatement 的核心在于“先编译、后填参”:数据库在 prepare 阶段就把 SQL 解析成执行计划,? 占位符只是预留的数据槽位,后续 setXXX() 传入的值走的是二进制参数通道,绝不会拼进 SQL 字符串里。
- 错误写法:"SELECT * FROM user WHERE name = '" + name + "'" —— 即使加了 replace("'", "") 或 parseInt(),依然危险
- 正确写法:conn.prepareStatement("SELECT * FROM user WHERE name = ?"),再调用 pstmt.setString(1, name)
- 数字类型也不能例外:用 setInt(2, userId),而非 "WHERE id = " + userId(防止整数溢出或未来误改引入风险)
所有用户可控输入都得走参数化
不只是登录、搜索这类显眼入口,任何可能带用户输入的地方都要覆盖:
Java JDK 25 来自 OpenJDK 官方归档,版本为 JDK 25,本条下载地址已指向官方 Windows x64 zip 安装包直链,适合调试旧项目或兼容旧版 Java 运行环境。
- WHERE 条件里的字段值(用户名、邮箱、状态码、时间范围)
- INSERT INTO ... VALUES (?) 的每一列
- UPDATE ... SET col = ? 的赋值侧
- DELETE ... WHERE ... 的过滤条件
- 即使只查一个 ID,也得用 "SELECT * FROM order WHERE id = ?",不能图省事拼字符串
别信“手动转义”或“简单过滤”
试图靠 replace("'", "''")、正则删掉分号、或只允许字母数字,都是徒劳且易漏的。攻击手段远超常见字符,比如:
- MySQL 中 1; DROP TABLE users 可能触发堆叠查询(如果驱动和数据库配置允许)
- 利用 LIKE 中的 % 和 _ 实现盲注,白名单校验拦不住
- 绕过前端限制,直接发 POST 请求提交恶意 payload
- PreparedStatement 不做转义,它根本不需要——因为数据根本不参与 SQL 解析
配合 ORM 框架时也要确认底层是否参数化
Hibernate/JPA 默认使用参数化查询,但要注意两类例外:
- 原生 SQL 查询(@Query(nativeQuery = true))必须手动写 ? 或 :param,不能拼接
- 动态表名、列名无法参数化(SQL 语法不允许),需用白名单严格限制(如只允许 "user", "order", "log")
- Criteria API 和 JPQL 自动参数化,是最安全的推荐写法
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










