LIKE查询不能直接拼接用户输入,因为%、_通配符与SQL注入点天然共存;必须使用参数化查询,如SELECT * FROM user WHERE name LIKE CONCAT('%', ?, '%')并用ps.setString(1, userInput)绑定。

LIKE查询为什么不能直接拼接用户输入
因为 LIKE 的通配符(%、_)和 SQL 注入点天然共存——一旦把用户输入用 + 或字符串拼接进 SQL,攻击者就能注入 ' OR 1=1 -- 或闭合引号后追加恶意语句。哪怕你手动加了 %,比如 "%" + userInput + "%",只要没走参数化,就等于敞开大门。
CONCAT函数本身不防注入,关键在参数绑定方式
CONCAT 是个幌子,真正起作用的是它如何被调用:只有在预编译语句(PreparedStatement)中作为 SQL 函数使用,且所有参数都走占位符绑定,才能隔离风险。如果写成 "SELECT * FROM user WHERE name LIKE CONCAT('%', '" + userInput + "', '%')",照样被注入。
正确姿势是:
SELECT * FROM user WHERE name LIKE CONCAT('%', ?, '%')
然后用 ps.setString(1, userInput) 绑定——此时 ? 被 JDBC 驱动当作纯数据处理,CONCAT 只是服务端函数,不会解释参数内容。
- MySQL 5.7+ 和 8.0 均支持该写法;低版本可用
CONCAT('%', ? ,'%')替代 - PostgreSQL 不支持
CONCAT这种用法,应改用'%' || ? || '%'并同样绑定参数 - Oracle 需用
'%' || ? || '%',且注意NULL与空字符串的差异
比CONCAT更安全、更通用的替代方案
其实没必要非用 CONCAT。多数场景下,直接在应用层拼好通配符再绑定更清晰,也兼容所有数据库:
SELECT * FROM user WHERE name LIKE ?
然后 ps.setString(1, "%" + userInput.trim() + "%")。前提是确保 userInput 已做基础清洗(如去首尾空格、拒绝空值),且绑定过程严格走 setString 等类型安全方法。
- 避免在 SQL 里用
TRIM()或IFNULL包裹参数,会干扰 JDBC 对参数位置的识别 - 若需前后模糊但中间固定(如
%abc%def%),仍应拆成多个?:CONCAT('%', ?, '%', ?, '%') - ESCAPE 子句(如
LIKE '\_%' ESCAPE '\')必须硬编码转义字符,不能参数化
容易忽略的边界情况:NULL、空字符串和特殊字符
用户输入为 null 或空字符串时,CONCAT('%', null, '%') 在 MySQL 中结果是 NULL,导致整条 WHERE 条件为 UNKNOWN,查不到任何数据——这未必是预期行为。
- Java 侧建议先判断:
if (input == null || input.trim().isEmpty()) { ... },再决定是否执行查询或返回空结果集 -
userInput含有\、'、"不影响绑定,但若业务要求用户能搜索这些字符本身,需提前约定转义规则(如前端双写反斜杠),后端不做额外转义 - LIKE 中的
_和%在参数化后仍是通配符,这是功能所需,不是漏洞;真要查字面量得配合ESCAPE,且 escape 字符必须写死
最麻烦的从来不是语法怎么写,而是想清楚“用户到底想搜什么”——是前缀匹配、包含匹配,还是大小写敏感?这些逻辑一旦混在 SQL 拼接里,就很难收场。










