唯一安全做法是所有用户输入必须走参数化查询,禁用字符串拼接;mysql推荐用concat('%', #{name}, '%'),多数据库项目可用标签组装pattern,业务层需校验输入并配合escape机制防通配符滥用。

唯一安全做法是:所有用户输入必须走参数化查询,不能拼进 SQL 字符串里。哪怕只是加两个 %,也不能用字符串拼接或 ${} 直接塞进去。
为什么 ${username} + '%' 是高危写法
因为 ${} 是文本替换,不是参数绑定。传入 admin' OR '1'='1,最终 SQL 就变成:
WHERE name LIKE '%admin' OR '1'='1%'
这会绕过条件查出全表数据,且无法被 JDBC 预编译拦截。
- 错误示例:
WHERE name LIKE '%${username}%' - 正确前提:必须用
#{username},且通配符由数据库函数或 MyBatis 内部处理 - MySQL 不支持
'%' || #{name} || '%'(那是 PostgreSQL/Oracle 语法),硬写会报错
MySQL 推荐用 CONCAT('%', #{name}, '%')
这是最直接、兼容性好、且能确保 #{name} 被当参数处理的方式。
- 传入
张%→ 实际执行:LIKE CONCAT('%', '张%', '%')→ 等价于LIKE '%张%%'(注意:后一个%是字面量) - 传入
admin' OR '1'='1→ 自动转义为'admin'' OR ''1''=''1',仍只是字符串值 - MySQL 5.0+ 全支持;若用极老版本,可改用
CONCAT_WS('', '%', #{name}, '%')
多数据库项目用 <bind></bind> 标签组装 pattern
避免依赖数据库函数,适合 Oracle、PostgreSQL、SQL Server 等混合环境。
- 写法:
<bind name="pattern" value="'%' + username + '%"></bind>,然后WHERE name LIKE #{pattern} - 关键点:
value里用的是 Java 表达式(+拼字符串),不是 SQL,MyBatis 在解析 XML 阶段就合成完整字符串再绑定 - 严禁写成
value="'%' + ${username} + '%'——${}还在表达式里,照样注入 - 参数类型需设为
parameterType="map"或用@Param注解传参
LIKE 中的通配符 % 和 _ 需要转义吗?
只在你**允许用户输入通配符本意**时才需要;否则,应禁止或过滤。
- 如果业务只要“包含某子串”,就别让用户输
%或_—— Controller 层校验长度 + 正则^[a-zA-Z0-9\u4e00-\u9fa5]+$更干脆 - 如果真要支持用户自定义通配符(比如高级搜索),必须用
ESCAPE机制:WHERE name LIKE #{pattern} ESCAPE '\',并在 Java 层对%、_做replace("%", "\%").replace("_", "\_") - 数据库侧写法示例:
CONCAT('%', REPLACE(REPLACE(#{name}, '%', '\%'), '_', '\_'), '%'),配合ESCAPE '\'
真正容易被忽略的,是把「参数化」当成万能药——它防得住 ' 和 ;,但防不住用户有意输 % 导致查出不该查的数据。业务语义层的约束,和数据库层的 escape,一个都不能少。











