SQL注入:MyBatis中#{}与${}安全差异与选型
理解#{}与${}:参数绑定机制决定安全边界
在MyBatis框架中,#{}与${}的核心差异源于底层参数绑定机制的不同。#{}采用预编译参数绑定(PreparedStatement),MyBatis在解析SQL时会将占位符替换为问号“?”,随后交由JDBC驱动在数据库层进行安全的类型转换与转义处理。这意味着无论用户输入何种特殊字符,数据库都会将其视为纯文本值,而非可执行的SQL指令。相反,${}执行的是纯粹的字符串拼接,MyBatis会在SQL编译前直接将变量值替换到XML或注解的文本中。例如,当传入参数为admin' OR '1'='1时,#{}生成的SQL仍为安全的WHERE name = ?,而${}则会直接拼接到语句中,破坏原有SQL结构。这种机制差异直接决定了两者在面对不可信用户输入时的安全边界:#{}天然具备防注入能力,而${}则将拼接风险完全暴露给开发者。

SQL注入风险:${}为什么容易踩坑
${}之所以成为SQL注入的高发区,根本原因在于它绕过了数据库的预编译保护,直接将外部输入嵌入到SQL语句的语法结构中。当开发者在动态查询中误用${}处理用户可控参数时,恶意输入即可轻易闭合原有引号并追加非法指令。典型危险场景包括:将前端传入的排序字段直接用于ORDER BY ${column},或将搜索关键词拼接到LIKE '%${keyword}%'中。若攻击者传入id; DROP TABLE users; --,数据库将直接执行破坏性操作。需要明确的是,#{}并非万能替代品。由于预编译机制的限制,#{}只能用于绑定SQL语句中的“值”(Value),无法用于替换表名、列名、排序关键字(ASC/DESC)或动态拼接的SQL片段。在这些必须动态生成SQL结构的场景中,盲目替换为#{}会导致语法错误,因此开发者必须正视${}的不可替代性,并在必须使用时建立严格的输入过滤机制。

实际选型:查询参数、排序与动态SQL怎么用
在实际开发中,参数选型应遵循“值用#{},结构用${}且严控”的核心原则。对于所有WHERE条件过滤、INSERT/UPDATE的字段赋值以及LIKE模糊查询,必须无条件使用#{},以利用预编译机制彻底阻断注入路径。当业务需求涉及动态表名、动态列名、排序方向(ASC/DESC)或分页偏移量时,由于数据库预编译协议不支持对SQL标识符进行参数化,只能使用${}。此时必须引入白名单校验机制:例如在排序场景中,预先定义允许排序的字段集合Set<String> validColumns = Set.of("create_time", "status", "id"),在拼接前校验用户输入是否严格匹配白名单,若不匹配则抛出异常或降级为默认排序。对于复杂的动态SQL,应优先使用MyBatis提供的<if>、<choose>、<foreach>等标签进行逻辑控制,而非依赖字符串拼接。通过“白名单拦截+动态标签替代”的组合策略,可在满足灵活性的同时守住安全底线。

验证与避坑:从SQL日志到安全测试
验证SQL安全性不能仅依赖代码审查,必须结合运行期日志与主动测试。首先,在开发环境开启MyBatis的SQL日志打印(如配置logging.level.org.mybatis=DEBUG),观察实际发送给数据库的完整SQL语句。若日志中显示#{}被替换为带引号的字符串且无语法异常,说明绑定正常;若${}拼接后出现未闭合引号或多余关键字,则存在注入隐患。其次,应使用边界值与标准注入Payload(如' OR '1'='1、admin'; DROP TABLE users;--)进行自动化单元测试,验证恶意输入是否被拦截或转义。在避坑实践中,常见误用包括在WHERE子句中滥用${}、未对动态列名做枚举校验、以及将${}与<if>混用导致空值拼接报错。代码审查时应建立检查清单:全局搜索${关键字,确认其仅用于标识符替换;强制要求所有${}变量必须经过白名单过滤或枚举限制;对涉及多租户或敏感数据的查询,增加SQL审计拦截器。通过日志追踪、自动化测试与规范化审查,可系统性消除动态SQL的安全盲区。

本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













