jdbctemplate 本身不防 sql 注入,关键在于正确使用 ? 占位符+参数绑定;字符串拼接、动态表名列名、in 动态长度等场景必须用白名单或 namedparameterjdbctemplate 防护。

JdbcTemplate 本身不防 SQL 注入,真正起作用的是你是否用对了参数绑定方式——只要用 ? 占位符 + 参数列表(或可变参数),底层就会走 PreparedStatement;一旦字符串拼接进 SQL,防护就彻底失效。
必须用 ? 占位符 + 显式参数传入
这是唯一能触发预编译的路径。JdbcTemplate 看到 ? 就会调用 Connection.prepareStatement(),后续所有参数都由 JDBC 驱动做类型安全绑定,输入里的 '、;、-- 全部当普通字符处理,不会进 SQL 解析器。
- ✅ 正确:位置参数 ——
jdbcTemplate.update("UPDATE user SET name = ? WHERE id = ?", name, id) - ✅ 正确:数组参数 ——
jdbcTemplate.query("SELECT * FROM user WHERE status = ? AND age > ?", new Object[]{status, minAge}, rowMapper) - ❌ 错误:字符串拼接 ——
jdbcTemplate.queryForInt("SELECT COUNT(*) FROM user WHERE id = " + id),哪怕id是数字,也等同于裸奔
IN 查询不能硬凑问号,得换 NamedParameterJdbcTemplate
原生 JdbcTemplate 不支持动态长度的 IN 列表。试图用 String.join() 拼出 IN (?, ?, ?) 再塞进 SQL,等于手动绕过参数绑定,JDBC 会报 Invalid argument value: java.io.NotSerializableException 或参数数不匹配。
- ✅ 正确:用
NamedParameterJdbcTemplate+MapSqlParameterSource——String sql = "SELECT * FROM user WHERE id IN (:ids)"; namedJdbcTemplate.query(sql, new MapSqlParameterSource("ids", Arrays.asList(1L, 2L, 3L)), rowMapper) - ⚠️ 注意:
:ids是一个整体参数,驱动会自动展开成对应数量的?并逐个绑定,不是字符串替换 - ❌ 错误:
"IN ('" + String.join("','", ids) + "')",这是高危写法,直接把用户输入当 SQL 片段执行
表名、列名、ORDER BY 字段不能用 ? 绑定
? 只能绑定「值」,不能绑定 SQL 结构。任何把用户输入拼进表名、字段名、排序方向的位置,哪怕后面跟了 ?,也完全不防注入。
- ❌ 错误:
"SELECT * FROM " + tableName + " WHERE id = ?" - ❌ 错误:
"SELECT * FROM user ORDER BY " + sortField + " " + sortOrder - ✅ 正确:白名单校验 —— 提前定义允许的字段集合,如
Set.of("name", "email", "created_time"),用ALLOWED_FIELDS.contains(sortField)断言,不通过直接拒掉 - ✅ 补充:枚举比字符串更安全,比如
SortField.NAME而非自由输入的"name"
最容易被忽略的是动态结构校验——很多人以为用了 ? 就万事大吉,结果在 FROM 或 ORDER BY 处留了后门。白名单不是可选项,是硬性前提;IN 场景不换工具,迟早出事。











