mybatis如何防止sql注入:用好{}占位符
mybatis如何防止sql注入,核心手段是优先使用#{}预编译占位符,让JDBC底层走PreparedStatement参数绑定,参数和SQL语句分开解析,大幅降低注入风险;${}直接字符串拼接不做转义,仅在字段名、表名这类无法预编译的场景有限使用,搭配自定义校验白名单才能降低风险,同时配合输入校验、最小权限数据库账号、关闭数据库危险函数形成多层防护,这套方案适合Java后端开发中使用MyBatis3.x版本做数据持久层的开发人员,不适合动态拼接复杂SQL且缺少参数校验、无数据库权限管控的临时脚本场景。
mybatis预编译占位符#{}的使用规则
你在MapperXML或者注解SQL里写#{paramName},MyBatis会把SQL模板提前交给数据库编译生成执行计划,传入的参数只会被当成纯文本值处理,数据库不会解析参数内部的SQL关键字。传入'or1=1--这类注入载荷时,它会被当成普通字符串存入字段,不会改变原有SQL逻辑。#{}支持自动处理字符串引号、日期格式转换,不需要手动添加单引号,手动加引号反而会引发语法报错。
示例写法:whereusername=#{username},对应的底层SQL结构固定为select*fromuserwhereusername=?,问号作为参数占位位置,参数单独传递。
mybatis中${}的风险与约束方案
${}会直接把参数内容原样拼接到SQL字符串再提交数据库,没有预编译隔离,是注入高发点。只有动态指定排序字段、动态切换查询表名这类不能用占位符的场景才允许使用${},这类场景没有办法交给PreparedStatement处理。使用${}时,你必须搭建静态白名单,提前枚举允许传入的字段、表名字符串,代码里先判断入参是否命中白名单,匹配失败直接抛出异常拒绝执行。
| 占位符 | 编译方式 | 注入风险 | 适用场景 |
|---|---|---|---|
| #{} | 预编译参数绑定 | 风险较低 | 查询条件值、数值、字符串参数 |
| ${} | 字符串直接拼接 | 风险偏高 | 表名、排序字段,需白名单校验 |
不要把用户直接输入的内容传给${}。
mybatis配套防护手段
业务层增加输入合法性校验,限制字符串长度、字符范围,过滤超出业务预期的特殊字符,这一层可以拦截大部分简单注入载荷。数据库连接账号遵循最小权限原则,业务账号只分配select、insert、update、delete必要权限,去掉drop、alter、exec等DDL、高危函数权限,就算注入突破持久层,也很难篡改库表结构。
开启数据库日志审计,记录SQL执行记录,方便事后追溯异常访问。
MyBatis动态标签也要遵循占位符规范。
if、where、foreach这类动态SQL标签内部的条件参数,统一使用#{},foreach遍历集合时#{item}同样走预编译,不会引入注入问题。动态标签只负责拼接SQL结构,参数部分依旧隔离,很多开发者会误以为动态标签自带防注入能力,忽略里面混用${}带来的漏洞。
该方法的适用边界是,预编译只能隔离参数内容,无法保护SQL结构本身。如果业务代码直接根据用户输入拼接SQL关键字、子查询语句,无论使用哪一种占位符,防护都会失效。
你可以借助代码审计工具,扫描项目中所有${},逐个复核是否配置白名单校验。
