You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

字符串拼接JPQL非参数值是否存SQL注入风险?如何解决?

关于JPA查询拼接表/关联名的SQL注入风险与解决方案

先看你给出的代码:

final String query = "SELECT id FROM " + getSimpleName() + " WHERE " + relationName + ".id = :successor"; 
final Query queryConcerned = this.entityManager.createQuery(query); 
query.setParameter("successor", successorId); 

Sonar给出的警告是:

Use a variable binding mechanism to construct this query instead of concatenation.

你提到自己拼接的不是参数值,而是表名和关联属性名,那这种写法到底有没有SQL注入风险?我们来拆解分析:


一、是否存在SQL注入风险?

答案是:取决于getSimpleName()和relationName的来源:

  • 如果这两个值是硬编码常量、枚举值,或者来自你完全可控的内部元数据(比如从实体类的注解中读取的属性名),那当前场景下没有注入风险——攻击者根本没有机会篡改这些值。
  • 但如果这两个值是来自外部输入(比如前端传参、用户配置、第三方接口返回),那风险极高!举个例子:如果relationName被攻击者传入users WHERE 1=1 --,拼接后的SQL会变成:
    SELECT id FROM User WHERE users WHERE 1=1 -- .id = :successor
    
    这里的--会注释掉后面的条件,最终查询会返回所有记录,完全绕过了原本的过滤逻辑,甚至可能被构造出更恶意的语句。

Sonar的警告其实是一种防御性提醒:它无法判断你拼接的内容是否绝对安全,所以默认禁止任何字符串拼接构建查询的写法——毕竟很多安全漏洞都是后续维护时引入的(比如某天有人把内部可控的relationName改成了前端传参)。


二、如何解决?

根据不同场景,推荐以下几种方案:

1. 使用JPA Criteria API(最推荐)

Criteria API是JPA提供的类型安全查询构建方式,完全不需要手动拼接字符串,JPA会自动处理表名、字段名的映射,从根源避免拼接风险:

CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Long> cq = cb.createQuery(Long.class);

// 获取实体Root(这里可以直接传入实体Class,或者通过类名反射获取)
Root<?> entityRoot = cq.from(Class.forName(getEntityFullName()));
// 关联指定的属性(relationName是实体类中的属性名,JPA会自动映射到数据库字段)
Join<?, ?> relationJoin = entityRoot.join(relationName);

// 构建查询逻辑
cq.select(entityRoot.get("id"));
cq.where(cb.equal(relationJoin.get("id"), cb.parameter(Long.class, "successor")));

// 执行查询
Query query = entityManager.createQuery(cq);
query.setParameter("successor", successorId);

这种方式不仅安全,还能在编译期发现属性名拼写错误,可读性和可维护性都很强。

2. 白名单校验(适合必须用字符串拼接的场景)

如果一定要保留字符串拼接的写法,必须对拼接的内容做严格的白名单校验,确保只有合法的表名/关联名才能被拼接:

// 预定义所有允许的实体名和关联名(根据你的业务场景维护)
Set<String> allowedEntityNames = Set.of("User", "Order", "Product");
Set<String> allowedRelationNames = Set.of("author", "customer", "seller");

String entityName = getSimpleName();
String relation = relationName;

// 校验合法性,不合法直接抛出异常
if (!allowedEntityNames.contains(entityName)) {
    throw new IllegalArgumentException("Invalid entity name: " + entityName);
}
if (!allowedRelationNames.contains(relation)) {
    throw new IllegalArgumentException("Invalid relation name: " + relation);
}

// 校验通过后再拼接查询
final String query = "SELECT id FROM " + entityName + " WHERE " + relation + ".id = :successor"; 
final Query queryConcerned = this.entityManager.createQuery(query); 
query.setParameter("successor", successorId);

这种方式通过白名单彻底阻断了恶意值注入的可能,适合一些查询逻辑灵活但数据源可控的场景。

3. 使用JPA命名查询(适合固定逻辑的查询)

如果你的查询逻辑是固定的,可以在实体类上定义命名查询,完全避免拼接操作:

// 在对应的实体类上添加命名查询注解
@Entity
@NamedQuery(
    name = "User.findIdByAuthorId",
    query = "SELECT u.id FROM User u WHERE u.author.id = :successor"
)
public class User {
    // 实体属性...
}

// 代码中直接调用命名查询
Query query = entityManager.createNamedQuery("User.findIdByAuthorId");
query.setParameter("successor", successorId);

命名查询的可读性极强,而且完全由JPA管理,不存在拼接风险,适合查询逻辑固定的业务场景。


总结

哪怕你当前的拼接内容是安全的,Sonar的警告也是在提醒你这种写法存在潜在的维护风险。优先选择Criteria API这种类型安全的方式,其次用白名单校验兜底,尽量避免直接拼接字符串构建查询。

内容的提问来源于stack exchange,提问作者Renaud is Not Bill Gates

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 06:43:18