preparedstatement对${}无效,因其在mybatis生成sql字符串阶段即完成文本替换,不进入预编译流程;数据库收到的是已拼接的完整sql,无?占位符,故无法防护。

PreparedStatement 本身不能防范 MyBatis 中 ${} 导致的注入漏洞——因为 ${} 根本不经过 PreparedStatement。
为什么 PreparedStatement 对 ${} 无效
${} 是 MyBatis 在 SQL 字符串生成阶段就完成的文本替换,发生在 JDBC 执行之前;PreparedStatement 的预编译流程此时早已结束。数据库最终收到的是一条完整拼好的 SQL,比如:
SELECT * FROM user WHERE name = 'admin' OR '1'='1'
这条语句根本没用到 ? 占位符,PreparedStatement 完全没参与,自然起不到防护作用。
真正有效的应对方式
防范 ${} 注入,核心不是靠 JDBC 层补救,而是在应用层切断恶意输入进入 SQL 模板的路径:
- 禁用所有来自不可信源的
${}:HTTP 参数、表单提交、URL 查询参数、MQ 消息等一律不准进${} - 必须使用
${}时,只允许极少数固定值,且强制白名单校验
例如排序字段:if (!List.of("id", "name", "create_time").contains(sortField)) throw new SecurityException("非法排序字段"); - 对动态表名、列名做正则限制,仅允许字母、数字、下划线,且长度可控
if (!sortField.matches("[a-zA-Z_][a-zA-Z0-9_]{0,31}")) { ... } - 数据库账号权限最小化:禁止执行 DROP、TRUNCATE、UNION SELECT 等高危操作,降低攻击后果
别指望过滤器或关键字黑名单
在 Servlet Filter 中拦截 "OR 1=1" 或 "--" 这类规则极易被绕过(如大小写变形、编码混淆、注释嵌套),属于低效且不可靠的补丁。防御重心必须前移到参数使用点——即 XML 或注解中是否该用 ${},以及它背后有没有可信来源保障。
替代方案优先选 #{} + 数据库函数
多数看似“必须用 ${}”的场景,其实有更安全的解法:
- 模糊查询不用
${keyword},改用CONCAT('%', #{keyword}, '%')(MySQL)或#{keyword} || '%'(PostgreSQL) - 动态 IN 列表不用
${ids},改用WHERE id IN <foreach>...</foreach>配合#{item} - 多表查询若需动态表名,应拆分为多个确定的 mapper 方法,而非一个泛用
${tableName}
安全不是靠某一层兜底,而是每一步都守住边界。#{} 是默认安全选项,${} 是例外通道——开启它,就得亲手把好门。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











