小易说IT 小易说IT

MyBatis-Plus 防 SQL 注入示例代码

核心结论:MP 自带的 QueryWrapper / LambdaQueryWrapper 条件构造器默认就是安全的,底层使用预编译参数绑定,不会 SQL 注入。 风险点只有一个:自己拼接字符串到 SQL 里(getSqlSelectapply${} 原生 xml)

1 ✅ 安全写法:LambdaQueryWrapper(推荐,编译期校验字段,最安全)

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;

@Service
public class UserService {

    @Resource
    private UserMapper userMapper;

    public User getUserByName(String username) {
        // Lambda 写法,直接引用实体类属性,不会写错字段名,底层预编译 ?
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
                .eq(User::getUsername, username);

        // 传入 payload: ' or '1'='1
        // 底层SQL:SELECT id,username,age FROM user WHERE username = ?
        return userMapper.selectOne(wrapper);
    }
}

2 ✅ 安全写法:普通 QueryWrapper(参数值安全,但字段名是字符串硬编码)

public User getUserByName(String username) {
    QueryWrapper<User> wrapper = new QueryWrapper<User>()
            .eq("username", username); // 值:username 使用预编译,安全

    return userMapper.selectOne(wrapper);
}

⚠️ 注意:eq("username", val) 第二个参数是查询值,安全;第一个参数是数据库字段名,是字符串,这里如果字段名来自外部用户输入,会有风险! 例:eq(userInputField, val) userInputField 如果是前端传的字段名,存在注入风险!要用白名单校验。

3 ⚠️ 高危:apply 方法(自定义 SQL 片段,容易注入)

apply 支持拼接 SQL,参数必须使用占位符 {0} 传参,不要直接字符串拼接

✅ apply 安全写法(使用占位符传参)

// 正确:参数用占位符,底层预编译
wrapper.apply("date(create_time) = {0}", "2026-09-15");

❌ apply 危险写法(字符串直接拼接,SQL 注入漏洞)

// 禁止!直接拼接字符串,payload 会被带入SQL
String dateVal = "2026-09-15' or 1=1 --";
wrapper.apply("date(create_time) = '" + dateVal + "'");

4 ⚠️ 动态排序 orderBy 场景(高频踩坑点)

MP 的 orderByAsc / orderByDesc

  • 参数是字段名,不是查询值;如果字段来自前端传入,必须白名单校验

✅ 安全示例(白名单校验动态排序字段)

// 允许排序的字段白名单
private static final Set<String> SORT_WHITE_LIST = Set.of("id", "username", "age");

public List<User> listUser(String sortField, boolean isAsc) {
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.eq("age", 18);

    // 白名单校验
    if (sortField != null && SORT_WHITE_LIST.contains(sortField)) {
        if (isAsc) {
            wrapper.orderByAsc(sortField);
        } else {
            wrapper.orderByDesc(sortField);
        }
    } else {
        // 默认排序
        wrapper.orderByDesc("id");
    }
    return userMapper.selectList(wrapper);
}

❌ 禁止:直接把前端传入的 sortField 丢进 orderByAsc,攻击者传 id;drop table user;-- 会造成风险。

5 ⚠️ 自定义 XML + MP 混合场景(和原生 MyBatis 一样规则)

#{} 安全

<select id="selectUser" resultType="User">
    SELECT id,username,age FROM user WHERE username = #{username}
</select>

${} 直接替换,注入风险,仅表名 / 排序字段可用,必须白名单。

6 ⚠️ 特殊方法:getSqlSelect / setSqlSelect

setSqlSelect 用来指定查询字段,字段字符串如果来自用户输入,存在注入风险。 ✅ 安全:固定字段

wrapper.setSqlSelect("id,username");

❌ 危险:用户输入字段直接拼接

// userInputCols 前端传入,风险!
wrapper.setSqlSelect(userInputCols);

7 完整测试注入 payload

// 注入测试 payload
String payload = "' or '1'='1";
User user = getUserByName(payload);
// 底层执行:WHERE username = ?,参数值就是 "' or '1'='1",不会解析成SQL语法,查不到数据
System.out.println(user);

总结 MP 防注入清单

写法

是否安全

说明

LambdaQueryWrapper.eq (实体::属性,值)

✅ 安全

推荐,编译校验字段

QueryWrapper.eq ("字段", 值)

✅ 值安全

字段名写死没问题;字段来自外部输入则危险

apply ("sql 片段", 参数)

✅ 占位符安全

禁止字符串拼接参数

orderByAsc (前端传入字段)

❌ 不安全

必须白名单过滤字段

setSqlSelect (前端传入字段)

❌ 不安全

白名单校验

XML #{param}

✅ 安全

预编译

XML ${param}

❌ 不安全

只能白名单场景使用

额外加固建议

  1. 业务层增加输入正则校验,限制特殊字符;

  2. 使用数据库最小权限账号;

  3. 开启 MyBatis-Plus 的 SQL 日志,生产环境关闭;

  4. 代码审计重点扫描:applyorderBysetSqlSelect、xml 中 ${


本文原创作者:易君召,详见:https://www.yijunzhao.cc/about,转载请注明出处。

原文链接 https://www.yijunzhao.cc/archives/mybatis-plus-sql-injection-prevention-example-code

欢迎访问 https://www.yijunzhao.cc/

https://www.yijunzhao.cc/