spring boot 3.0 中sql注入防护关键在于全程使用参数化查询,禁用字符串拼接、${}替换及动态表名/列名;jdbctemplate须用?占位符,@query(nativequery=true)需严守参数规则,动态结构必须白名单校验。

Spring Boot 3.0 中只要用对参数化查询机制,SQL 注入就基本防住了;翻车几乎全是因为绕过了参数绑定——比如拼字符串、硬塞 ${}、或把表名当参数传。
用 JdbcTemplate 时必须走 ? 占位符 + 参数列表
JdbcTemplate 在 Spring Boot 3.0 中默认调用 Connection.prepareStatement(),但只在 SQL 字符串含 ? 且参数通过方法签名或数组传入时才生效。
- ✅ 正确:
jdbcTemplate.query("SELECT * FROM user WHERE status = ? AND age > ?", new Object[]{status, minAge}, rowMapper) - ✅ 正确(可变参数):
jdbcTemplate.update("UPDATE user SET name = ? WHERE id = ?", name, id) - ❌ 错误:字符串拼接 ——
"UPDATE user SET name = '" + name + "' WHERE id = " + id,直接失效防护 - ⚠️ 注意:参数值里含
' OR 1=1 --没关系,JDBC 驱动会把它当纯字符串绑定,不会进语法解析阶段
@Query(nativeQuery = true) 是高危区,必须严格守参数规则
原生 SQL 绕过 Hibernate 的解析层,完全依赖你手写的安全性。它不支持 :ids 这种集合自动展开,也不接受运行时拼表名。
- ✅ 安全写法:
@Query(value = "SELECT * FROM user WHERE dept_id = ?1 AND status IN (?2, ?3)", nativeQuery = true),再传三个参数 - ❌ 危险写法:
@Query(value = "SELECT * FROM " + tableName + " WHERE id = ?", nativeQuery = true),表名拼接即破防 - ❌ 危险写法:
@Query(value = "SELECT * FROM user WHERE name LIKE '%${name}%'", nativeQuery = true),${name}是字符串替换,不是参数绑定 - ? 动态 IN 列表?别拼问号个数,改用
NamedParameterJdbcTemplate+:ids参数,它会自动展开成对应数量的?
JPA 派生方法和 @Query 非原生查询天然安全
这些方式由 Hibernate 在运行时生成 PreparedStatement,连占位符都不用你写,只要不碰 nativeQuery = true 或字符串拼接,就默认免疫。
- ✅ 安全:
findByEmailAndStatus(String email, String status)、findAllByRoleIn(List<string> roles)</string> - ✅ 安全:
@Query("SELECT u FROM User u WHERE u.username = :username")+@Param("username") - ✅ 安全:
@Query("SELECT u FROM User u WHERE u.id IN :ids"),集合参数会被 Hibernate 拆成多个? - ❌ 破防点:在
@Query里混用${}、#{}做字段/表名插值,或者用StringBuilder拼完再塞进@Query
动态结构(ORDER BY / 表名 / 列名)必须白名单校验
参数绑定只对「值」有效,对 SQL 结构无效。用户输入一旦用于拼接语句骨架,再怎么加 ? 都没用。
- ❌ 不安全:
"SELECT * FROM user ORDER BY " + sortField,哪怕sortField = "name DESC"也可能带恶意内容 - ✅ 正确做法:定义枚举或字符串白名单,如
Set.of("name", "created_at", "status"),校验后硬编码进 SQL - ✅ 替代方案:用
CriteriaBuilder构造查询,字段名来自 Java 属性反射,没有字符串插槽 - ⚠️ 注意:白名单要覆盖所有可能路径,包括 API 参数、前端传来的排序字段、甚至内部服务透传的元数据
真正难的不是写出一个能跑的参数化查询,而是确保整个调用链上没有任何一环偷偷把用户输入塞进了 SQL 字符串里——尤其在多层封装、动态条件组装、或对接老代码时,这种“悄悄拼接”最容易被忽略。











