Spring Data JPA常规查询(如派生方法、@Query非原生+命名参数)天然防SQL注入,因Hibernate自动生成PreparedStatement;但@Query(nativeQuery = true)、字符串拼接、动态SQL结构(如ORDER BY字段)需严格校验,否则失效。

只要不用字符串拼接 SQL,Spring Data JPA 默认就是防注入的;但一旦你写 @Query(nativeQuery = true) 或手动拼 StringBuilder,防线就立刻失效。
哪些 JPA 查询方式天然免疫 SQL 注入
Spring Data JPA 的常规查询方法(非原生 SQL)由 Hibernate 自动生成预编译语句,参数全程走 PreparedStatement 绑定,不拼字符串,因此无需额外处理就能防注入。
-
findByUsername(String username)、findByStatusAndRole(String status, String role)等派生查询:参数自动绑定,安全 -
@Query("SELECT u FROM User u WHERE u.email = :email")+@Param("email"):命名参数经 Hibernate 解析后转为预编译占位符,安全 -
@Query("SELECT u FROM User u WHERE u.id IN :ids")+@Param("ids") List<long></long>:集合参数会被 Hibernate 拆成对应数量的?占位符,安全 - 所有
JpaRepository自带方法(如save()、findById()、findAll(Specification)):底层调用标准 JPA API,不暴露 SQL 构造过程
为什么 @Query(nativeQuery = true) 是高危操作
原生 SQL 绕过 Hibernate 的解析层,直接交给 JDBC 执行。如果你在其中用字符串拼接传入的变量,就等于把大门敞开。
- 错误示范:
@Query(value = "SELECT * FROM user WHERE name = '" + name + "'", nativeQuery = true)—— 直接触发注入 - 正确做法:必须用参数占位符
?或命名参数:name,且仅通过@Param传值 - 注意:原生 SQL 中不支持
IN动态列表直接绑定(如WHERE id IN :ids会报错),需改用JdbcTemplate或构造固定长度占位符 - 示例安全写法:
@Query(value = "SELECT * FROM user WHERE status = ?1 AND dept_id IN (?2, ?3)", nativeQuery = true),然后传入三个参数
动态条件 + 原生 SQL 的常见翻车点
业务常需要“按需添加 WHERE 条件”,有人会用 StringBuilder 拼 SQL 字符串再塞进 @Query,这是最典型的自毁行为。
- 问题根源:JPA 的
@Query是编译期固定的,无法运行时改变结构;所谓“动态”只能靠外部拼接字符串,而拼接即破防 - 替代方案一:用
JpaSpecificationExecutor+Specification,条件由类型安全的 Java 代码构建,最终仍生成预编译语句 - 替代方案二:用
JdbcTemplate+PreparedStatementCreator,手动控制参数绑定,但必须严格避免字符串插值 - 绝对禁止:
String sql = "SELECT * FROM t WHERE 1=1" + (status != null ? " AND status = '" + status + "'" : "")
容易被忽略的“伪安全”场景
有些写法看似用了参数,实则仍在拼 SQL,尤其在复杂排序、字段名、表名等非值类位置。
-
ORDER BY后不能参数化:如@Query("... ORDER BY :sortField")是无效的,Hibernate 不允许参数化列名;必须白名单校验或枚举映射 - 表名、字段名、
GROUP BY子句、UNION结构等都不能用@Param绑定,它们不属于“值”,而是 SQL 结构的一部分 - 使用
Pageable时,Sort.by("user_name")若直接来自请求参数,需提前校验字段是否在白名单内(如Set.of("username", "created_time")) - MyBatis-Plus 的
QueryWrapper同理:其eq("username", input)安全,但orderByAsc(inputField)必须过滤
真正的防御不在“加了多少层校验”,而在“有没有让任意用户输入直接参与 SQL 结构生成”。哪怕只放行一个未过滤的排序字段,整套防护就形同虚设。

















