sql注入本质是程序将用户输入直接拼入sql语句,导致恶意代码被数据库执行;如输入' or '1'='1使查询恒真绕过认证。安全做法是使用preparedstatement的?占位符,通过setstring()等方法绑定参数,由驱动自动转义和类型校验。

为什么直接拼接SQL字符串会触发SQL注入
因为 Statement 执行的是纯文本 SQL,用户输入的单引号、分号、UNION SELECT 等内容会被数据库原样解析执行。比如用户在登录表单输入用户名 ' OR '1'='1,拼出来的 SQL 就变成 SELECT * FROM users WHERE name = '' OR '1'='1',绕过认证。
PreparedStatement 的占位符怎么写才安全
必须用 ? 占位,不能把变量插进 SQL 字符串里。每个 ? 对应一个参数,通过 setString()、setInt() 等方法绑定值,JDBC 驱动会自动转义并按类型处理:
String sql = "SELECT * FROM users WHERE name = ? AND age > ?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, request.getParameter("name")); // 自动加引号、转义单引号
ps.setInt(2, Integer.parseInt(request.getParameter("age"))); // 拒绝非数字输入
-
?只能用于值的位置,不能用于表名、列名、ORDER BY 子句 —— 这些需白名单校验后拼接 - 索引从 1 开始,不是 0
- 如果参数是
null,用setNull(1, Types.VARCHAR),别传null给setString
迁移时最容易漏掉的三类地方
很多项目只改了显眼的查询语句,但下面这些位置同样危险:
- DAO 层的
update/delete方法,尤其带条件拼接的(如动态 WHERE) - 批量操作:用
addBatch()+executeBatch()时,仍要基于同一个PreparedStatement实例,不能在循环里反复prepareStatement("INSERT ... VALUES ('" + x + "')") - 使用 JSP 脚本片段(
)内联的 JDBC 代码 —— 这类老代码常被忽略,但风险最高
不支持 PreparedStatement 的边界情况怎么办
像 MySQL 的 SET NAMES utf8mb4、PostgreSQL 的 SET LOCAL statement_timeout 这类 DDL 或会话级命令,JDBC 不允许用 ? 占位。此时必须:
- 禁止接收用户输入参与构造
- 若必须动态,只允许从预设白名单中选值,例如:
if (!Arrays.asList("utf8", "utf8mb4").contains(charset)) throw new IllegalArgumentException(); - 避免用
Statement执行任意用户可控字符串 —— 即使加了过滤也不可靠
真正难的不是换掉 Statement,而是识别出所有执行 SQL 的入口点,尤其是那些藏在工具类、日志埋点、异常处理回调里的隐式调用。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











