PreparedStatement能彻底防止SQL注入,因其先编译SQL模板、后通过二进制参数通道传值,用户输入绝不参与SQL解析;但表名、列名等非参数位置仍需白名单校验。

用 PreparedStatement 替换 Statement 是底线
直接拼接 SQL 字符串(比如 "SELECT * FROM user WHERE name = '" + name + "'")在 Java 17 里依然会出事,和 JDK 版本无关。关键不是“Java 17 新特性”,而是你有没有把 Statement 换成 PreparedStatement,并真正用上占位符。
常见错误是:只改了创建方式,但没改参数传入逻辑。比如写了 PreparedStatement,却还在 SQL 字符串里拼接变量——这等于没修。
-
PreparedStatement必须配合?占位符使用,不能出现在 SQL 字符串中 - 所有用户输入都必须走
setString()、setInt()等方法绑定,不能拼进 SQL - 连接池(如 HikariCP)不影响这个原则,预编译逻辑在 JDBC 驱动层完成
MyBatis 中 #{} 和 ${} 的坑必须分清
MyBatis 在 Java 17 项目里仍是主流,但很多人误以为用了 MyBatis 就自动免疫 SQL 注入——错在 ${} 上。
${} 是字符串替换,不经过预编译;#{} 才是预编译参数化。只要用了 ${field} 做 ORDER BY、GROUP BY 或表名/列名拼接,就等于开了后门。
- 能用
#{}的地方绝不用${}——比如普通 WHERE 条件、数值比较 - 真需要动态列名(如
ORDER BY ${sortField}),必须加白名单校验:if (!Arrays.asList("id", "name", "create_time").contains(sortField)) throw new IllegalArgumentException(); -
标签可以辅助处理模糊查询,比如后再用#{likeName},避免在 XML 里拼"%" + name + "%"
Like 查询和 in 参数容易漏掉
这两个场景常被当成“已修复”,实际仍高危。比如写成 "WHERE name LIKE '%" + name + "%",或 "IN (" + idsString + ")",预编译完全失效。
Java 17 没提供新语法来绕过这个问题,只能靠编码习惯和工具约束。
-
LIKE参数必须用setString()绑定完整值,通配符由 Java 拼好再传入:ps.setString(1, "%" + userInput + "%"); -
IN列表不能硬拼,要用循环+多个?占位符,或改用 MyBatis 的标签(它内部生成正确数量的?) - 别信“我过滤了单引号就安全了”——
UNION SELECT、OR 1=1不依赖引号也能生效
别指望全局过滤器兜底
有人在 Filter 里对所有请求参数做 replace("'", "''") 或转全角,这种做法在 Java 17 项目里依然常见,但根本不可靠。
它既破坏业务语义(比如用户真想搜 “O’Reilly”),又挡不住无引号注入(1 OR 1=1)、宽字节绕过、或 JSON 入参里的嵌套 SQL 片段。
- 过滤器适合做日志脱敏或前端 XSS 防御,不适合当 SQL 注入主防线
- 如果项目里存在无法改造的遗留动态 SQL(比如报表引擎),唯一办法是严格限制数据库账号权限——只给
SELECT,禁用UNION、LOAD_FILE、存储过程调用等高危能力 - Java 17 的
SecurityManager已被移除,没法靠运行时拦截 SQL 字符串,防御必须落在编码层
最麻烦的不是写错,而是修一半:看到 PreparedStatement 就以为万事大吉,结果在某个 ORDER BY 或 IN 场景里悄悄回退到字符串拼接。这类漏洞不会报错,只会在渗透测试或线上日志里突然暴露。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











