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

如何改造Java字符串拼接SQL代码以防范SQL注入漏洞?

解决Java项目SQL注入漏洞:参数化查询落地方案

你的核心问题是把字符串拼接生成SQL的逻辑改成参数化查询,核心思路是将用户输入的动态值从SQL模板中剥离,用占位符代替,再通过安全的方式将参数绑定到查询中。以下是针对你提到的代码结构(Controller层getSearchResults、SQL构建方法getSearchQuery/buildSQLWhereClause)的具体改写方案:

一、重构SQL构建逻辑:分离SQL模板与参数列表

原来的buildSQLWhereClause直接拼接用户输入到SQL片段里,现在要改成生成带占位符的SQL片段,同时收集对应的参数值。

示例改写(原生Java)

// 改写buildSQLWhereClause:返回带占位符的SQL片段 + 参数列表
private Pair<String, List<Object>> buildSQLWhereClause(String username, String status) {
    StringBuilder whereFragment = new StringBuilder();
    List<Object> params = new ArrayList<>();

    if (username != null && !username.isBlank()) {
        whereFragment.append(" AND username = ?");
        params.add(username);
    }
    if (status != null && !status.isBlank()) {
        whereFragment.append(" AND status = ?");
        params.add(status);
    }

    return new Pair<>(whereFragment.toString(), params);
}

// 改写getSearchQuery:组合基础SQL与动态片段,返回完整SQL模板和参数
private Pair<String, List<Object>> getSearchQuery(String username, String status) {
    String baseSql = "SELECT id, username, status, email FROM users WHERE 1=1";
    Pair<String, List<Object>> wherePart = buildSQLWhereClause(username, status);
    
    String fullSql = baseSql + wherePart.getFirst();
    return new Pair<>(fullSql, wherePart.getSecond());
}

注:如果项目没有Pair类,可以自定义一个简单的SqlAndParams类来封装SQL字符串和参数列表。

二、在数据访问层执行参数化查询

根据项目使用的技术栈,选择对应的参数化执行方式:

1. 原生JDBC + PreparedStatement

直接使用JDBC的PreparedStatement绑定参数,避免SQL注入:

public List<User> getSearchResults(String username, String status) throws SQLException {
    Pair<String, List<Object>> sqlAndParams = getSearchQuery(username, status);
    String sql = sqlAndParams.getFirst();
    List<Object> params = sqlAndParams.getSecond();

    // 用try-with-resources自动关闭资源
    try (Connection conn = getDatabaseConnection()) {
        try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
            // 绑定参数:索引从1开始
            for (int i = 0; i < params.size(); i++) {
                pstmt.setObject(i + 1, params.get(i));
            }

            try (ResultSet rs = pstmt.executeQuery()) {
                List<User> results = new ArrayList<>();
                while (rs.next()) {
                    // 映射ResultSet到实体类
                    User user = new User();
                    user.setId(rs.getInt("id"));
                    user.setUsername(rs.getString("username"));
                    user.setStatus(rs.getString("status"));
                    user.setEmail(rs.getString("email"));
                    results.add(user);
                }
                return results;
            }
        }
    }
}

2. Spring JdbcTemplate(Spring项目推荐)

Spring的JdbcTemplate已经封装了PreparedStatement的细节,代码更简洁:

@Autowired
private JdbcTemplate jdbcTemplate;

public List<User> getSearchResults(String username, String status) {
    Pair<String, List<Object>> sqlAndParams = getSearchQuery(username, status);
    String sql = sqlAndParams.getFirst();
    Object[] params = sqlAndParams.getSecond().toArray();

    // 用RowMapper映射结果
    return jdbcTemplate.query(sql, params, (rs, rowNum) -> {
        User user = new User();
        user.setId(rs.getInt("id"));
        user.setUsername(rs.getString("username"));
        user.setStatus(rs.getString("status"));
        user.setEmail(rs.getString("email"));
        return user;
    });
}

3. MyBatis(ORM框架场景)

如果项目用MyBatis,直接用#{}占位符代替字符串拼接,框架会自动处理参数化:

Mapper接口

public interface UserMapper {
    List<User> searchUsers(@Param("username") String username, @Param("status") String status);
}

XML映射文件

<select id="searchUsers" resultType="com.yourpackage.User">
    SELECT id, username, status, email FROM users
    WHERE 1=1
    <if test="username != null and username != ''">
        AND username = #{username}
    </if>
    <if test="status != null and status != ''">
        AND status = #{status}
    </if>
</select>

注意:这里用#{}而不是${},${}会直接拼接字符串,依然存在注入风险。

三、特殊场景处理:动态表名/列名

如果你的SQL拼接涉及动态表名或列名(比如用户指定排序字段),这类场景不能用占位符,必须做白名单校验:

// 校验列名是否在允许的白名单内
private boolean isValidSortColumn(String column) {
    Set<String> allowedColumns = Set.of("id", "username", "status", "create_time");
    return allowedColumns.contains(column.toLowerCase());
}

// 合法后再拼入SQL
public String getSortedSearchSql(String sortColumn) {
    String baseSql = getSearchQuery(null, null).getFirst();
    if (isValidSortColumn(sortColumn)) {
        return baseSql + " ORDER BY " + sortColumn;
    }
    // 默认排序
    return baseSql + " ORDER BY create_time DESC";
}

关键原则

  • 所有来自用户/外部系统的输入,绝对不能直接拼入SQL字符串;
  • 优先使用ORM框架(MyBatis、JPA)的参数化查询能力,减少手动编写JDBC代码的出错概率;
  • 禁止使用Statement执行拼接后的SQL,必须用PreparedStatement或等效的参数化查询方式。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 04:37:32