核心原则是所有用户输入值必须参数绑定,动态表名或字段名须白名单校验;泛型查询方法接收预构建的QueryWrapper,禁止字符串拼接SQL,JOIN的ON条件、枚举字段、模糊查询等均需严格参数化与校验。

核心原则只有一条:所有用户输入值必须走参数绑定,动态表名或字段名必须用白名单硬控,不能因为“只是 ON 条件”或“字段来自主表”就跳过防护。
泛型查询方法的参数化设计
定义泛型方法时,不拼接 SQL 字符串,而是接收一个预构建的 QueryWrapper<T> 或 LambdaQueryWrapper<T>,由调用方负责条件组装。方法内部只做关联逻辑和分页处理,所有 WHERE、ON、HAVING 中的值均来自 wrapper 的参数化表达式。
- 禁止在泛型方法里用字符串格式化插入用户值,例如
"AND u.status = '" + status + "'" - 支持传入多个 wrapper(如主表 wrapper + 关联表 wrapper),分别绑定各自表的字段,避免混用导致漏绑
- 对枚举类字段(如 order_status)强制校验白名单,未匹配则抛异常,不传入 SQL
多表 JOIN 中的 ON 条件必须全部参数化
LEFT JOIN、INNER JOIN 的 ON 子句不是“安全区”。只要其中出现用户输入,就必须参数化。比如按运单号查物流+订单,ON l.logistics_no = ? 中的 ? 必须绑定前端传入的 logisticsNo,不能写成 ON l.logistics_no = '${logisticsNo}'。
- SqlSugar 的
JoinQueryInfos、MyBatis-Plus 的leftJoin()方法本身不自动参数化,需确保传入的 Lambda 表达式中所有右侧值均为变量引用,而非字符串拼接 - 若使用
@Select配合${ew.customSqlSegment},要确认该 wrapper 已完整覆盖所有跨表过滤字段,包括关联键本身 - 测试时可将任意参数设为
' OR 1=1 --,验证是否返回非预期全量数据
动态表名与字段名必须白名单校验
泛型方法若支持切换主表(如查用户 or 查商品),表名不能来自请求参数直接拼接。需预先定义合法表名集合,用 switch 或 Map 映射到实体类与别名。
- 字段排序(ORDER BY)、分组(GROUP BY)同理,只允许从固定字段列表中选取,禁用 ${} 插入
- LIKE 模糊查询需统一转义通配符:
wrapper.like("name", "%" + escapeLikeValue(input) + "%") - 对时间范围、数值区间等结构化参数,解析后做类型强转(如 Long.parseLong),失败则拒绝执行
自动化安全引擎的关键校验环节
在泛型查询执行前插入统一拦截点,对 wrapper 内容做静态分析和运行时检查:
- 扫描所有条件表达式,识别是否存在未绑定的字符串插值(如含
"'"+xxx+"'"的痕迹) - 检查是否调用了危险 API:MyBatis 的
${}、Hibernate 的addCriteria(String)、JDBC 的Statement.execute() - 启用 MyBatis-Plus 的
sql-injector插件,在开发环境强制记录每条生成 SQL,人工抽检绑定位置

















