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

如何解决通过替换子串拼接SQL语句时的SQL注入问题?

解决SQL注入风险的方案

你的代码直接通过replaceAll将外部传入的studentFilter字符串拼接进SQL语句,这是典型的SQL注入高危操作——只要studentFilter被恶意构造(比如"1=1; DROP TABLE student;"),就会直接执行危险SQL。下面给出几种安全的替代方案:

方案一:用结构化参数替代字符串条件

修改方法参数,让调用方传入键值对形式的过滤条件,动态构建参数化SQL,彻底避免拼接字符串:

private Long getStudentCount(Map<String, Object> studentFilters) {
    private final NamedParameterJdbcTemplate template; // 类级别声明
    StringBuilder sqlBuilder = new StringBuilder("select count(name) from student where marks > 90 AND dateOfBirth = :dob");
    MapSqlParameterSource sqlParams = new MapSqlParameterSource();
    sqlParams.addValue("dob", "19900101");

    if (!studentFilters.isEmpty()) {
        sqlBuilder.append(" AND (");
        int paramIndex = 0;
        for (Map.Entry<String, Object> entry : studentFilters.entrySet()) {
            String paramName = "filterParam" + paramIndex++;
            // 动态添加参数化条件
            sqlBuilder.append(entry.getKey()).append(" = :").append(paramName).append(" AND ");
            sqlParams.addValue(paramName, entry.getValue());
        }
        // 移除末尾多余的" AND "
        sqlBuilder.setLength(sqlBuilder.length() - 5);
        sqlBuilder.append(")");
    }

    return template.queryForObject(sqlBuilder.toString(), sqlParams, Long.class);
}

调用时传入Map.of("schoolName", "ABCD", "state", "TEXAS")即可,所有值都会通过NamedParameterJdbcTemplate安全参数化。

方案二:严格校验+解析字符串条件(必须保留字符串参数时)

如果无法修改参数格式,必须处理studentFilter字符串,需做白名单校验+值参数化:

private Long getStudentCount(String studentFilter) {
    private final NamedParameterJdbcTemplate template;
    // 定义允许使用的字段白名单,防止恶意字段注入
    Set<String> allowedFields = Set.of("schoolName", "state", "grade");
    
    String[] conditions = studentFilter.split("AND");
    StringBuilder sqlFragment = new StringBuilder();
    MapSqlParameterSource sqlParams = new MapSqlParameterSource();
    sqlParams.addValue("dob", "19900101");
    
    int paramIndex = 0;
    for (String condition : conditions) {
        condition = condition.trim();
        String[] parts = condition.split("=");
        if (parts.length != 2) {
            throw new IllegalArgumentException("过滤条件格式非法");
        }
        String field = parts[0].trim();
        // 校验字段是否在白名单内
        if (!allowedFields.contains(field)) {
            throw new IllegalArgumentException("不允许使用的字段:" + field);
        }
        // 去除值的前后引号,提取纯值
        String value = parts[1].trim().replaceAll("^['\"]|['\"]$", "");
        String paramName = "filter" + paramIndex++;
        sqlFragment.append(field).append(" = :").append(paramName).append(" AND ");
        sqlParams.addValue(paramName, value);
    }

    String sql = "select count(name) from student where marks > 90 AND dateOfBirth = :dob";
    if (sqlFragment.length() > 0) {
        sqlFragment.setLength(sqlFragment.length() - 5);
        sql += " AND (" + sqlFragment + ")";
    }

    return template.queryForObject(sql, sqlParams, Long.class);
}

这个方案通过白名单限制可用字段,同时将所有值转为参数,避免直接拼接。

方案三:用Spring Data JPA动态查询(若项目使用JPA)

如果项目基于Spring Data JPA,可通过Specification动态构建安全查询:

// 实体类示例
@Entity
public class Student {
    @Id
    private Long id;
    private String name;
    private Integer marks;
    private String dateOfBirth;
    private String schoolName;
    private String state;
    // getter、setter省略
}

// Repository接口
public interface StudentRepository extends JpaRepository<Student, Long>, JpaSpecificationExecutor<Student> {}

// 业务层方法
private Long getStudentCount(Map<String, Object> studentFilters) {
    Specification<Student> spec = (root, query, cb) -> {
        List<Predicate> predicates = new ArrayList<>();
        // 固定条件
        predicates.add(cb.greaterThan(root.get("marks"), 90));
        predicates.add(cb.equal(root.get("dateOfBirth"), "19900101"));
        // 动态过滤条件
        for (Map.Entry<String, Object> entry : studentFilters.entrySet()) {
            predicates.add(cb.equal(root.get(entry.getKey()), entry.getValue()));
        }
        return cb.and(predicates.toArray(new Predicate[0]));
    };
    return studentRepository.count(spec);
}

核心原则

无论哪种方案,都要绝对避免直接将外部可控字符串拼接进SQL语句,所有动态值必须通过参数化方式传递,这是防止SQL注入的根本手段。

内容的提问来源于stack exchange,提问作者Shankar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 12:10:38