如何解决通过替换子串拼接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
相关产品推荐
相关产品推荐

