小易说IT 小易说IT

Java 防止 SQL 注入代码示例

核心原则:永远不要直接拼接字符串构造 SQL,优先使用 PreparedStatement(预编译语句),参数使用占位符 ?;MyBatis 使用 #{},禁止 ${}

一、原生 JDBC PreparedStatement(推荐,防注入)

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class JdbcSafeDemo {
    public static User getUserById(String username) {
        String url = "jdbc:mysql://127.0.0.1:3306/testdb?useSSL=false";
        String user = "root";
        String password = "123456";

        // ✅ 使用 ? 占位符,参数预编译,不会把参数当成SQL执行
        String sql = "SELECT id, username, age FROM user WHERE username = ?";

        try (Connection conn = DriverManager.getConnection(url, user, password);
             PreparedStatement pstmt = conn.prepareStatement(sql)) {

            // 设置参数,JDBC会自动转义特殊字符
            pstmt.setString(1, username);

            try (ResultSet rs = pstmt.executeQuery()) {
                if (rs.next()) {
                    User u = new User();
                    u.setId(rs.getLong("id"));
                    u.setUsername(rs.getString("username"));
                    u.setAge(rs.getInt("age"));
                    return u;
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    static class User {
        private Long id;
        private String username;
        private Integer age;
        // getter setter
        public Long getId() { return id; }
        public void setId(Long id) { this.id = id; }
        public String getUsername() { return username; }
        public void setUsername(String username) { this.username = username; }
        public Integer getAge() { return age; }
        public void setAge(Integer age) { this.age = age; }
    }

    public static void main(String[] args) {
        // 注入测试 payload: ' or '1'='1
        String payload = "' or '1'='1";
        User user = getUserById(payload);
        // 不会查询到数据,因为整个payload会被当做【字符串值】,不会解析成SQL语法
        System.out.println(user);
    }
}

危险写法(千万不要写!字符串拼接 SQL,会 SQL 注入)

// 危险!!不要使用!
String sql = "SELECT * FROM user WHERE username = '" + username + "'";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);

二、MyBatis 安全写法(最常用)

#{} 预编译(安全)

<!-- 安全:#{username} 使用PreparedStatement占位符 -->
<select id="selectUser" resultType="User">
    SELECT id,username,age FROM user WHERE username = #{username}
</select>

${} 直接字符串替换(存在注入风险,除非特殊场景)

<!-- 危险!${username} 直接字符串拼接,会SQL注入 -->
<select id="selectUser" resultType="User">
    SELECT id,username,age FROM user WHERE username = ${username}
</select>

补充:${} 仅适合数据库表名、排序字段这类不能用占位符的场景,使用时必须做白名单校验!

三、Spring JdbcTemplate 安全示例

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

public class JdbcTemplateDemo {
    private final JdbcTemplate jdbcTemplate;

    public JdbcTemplateDemo(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public User findUser(String username) {
        String sql = "SELECT id,username,age FROM user WHERE username = ?";
        RowMapper<User> rowMapper = (rs, rowNum) -> {
            User u = new User();
            u.setId(rs.getLong("id"));
            u.setUsername(rs.getString("username"));
            u.setAge(rs.getInt("age"));
            return u;
        };
        // 参数放在可变参数,底层还是PreparedStatement
        return jdbcTemplate.queryForObject(sql, rowMapper, username);
    }
}

四、额外防御手段(多层防护)

  1. 输入校验:前端 + 后端校验参数格式,用户名只允许字母数字下划线,拒绝单引号、--union 等敏感字符

// 简单校验示例
public static boolean validUsername(String username) {
    return username.matches("^[a-zA-Z0-9_]{1,32}$");
}
  1. 最小权限账号:数据库业务账号不要给 dropalter 等高权限

  2. 禁止错误堆栈对外暴露,防止泄露表结构

  3. ORM 框架(MyBatis-Plus、JPA)默认参数绑定都是安全的,不要自己拼接原生 SQL 字符串

五、当必须动态拼接字段 / 表名(${} 场景)白名单示例

场景:动态排序 order by ${sortField},不能用?占位符,必须白名单

// 白名单,只允许这几个字段排序
private static final Set<String> SORT_WHITE_LIST = Set.of("id", "username", "age");

public List<User> listUser(String sortField) {
    // 不在白名单直接拒绝
    if (!SORT_WHITE_LIST.contains(sortField)) {
        throw new IllegalArgumentException("非法排序字段");
    }
    String sql = "SELECT * FROM user ORDER BY " + sortField;
    // ...执行
}

本文原创作者:易君召,详见:https://www.yijunzhao.cc/about,转载请注明出处。

原文链接 https://www.yijunzhao.cc/archives/java-sql-injection-prevention-code-examples

欢迎访问 https://www.yijunzhao.cc/

https://www.yijunzhao.cc/