使用jdbc拼接条件查询语句时如何防止sql注入

本文通过对比Statement和PreparedStatement,展示了如何在拼接SQL查询条件时防止SQL注入攻击。使用PreparedStatement结合参数列表,可以动态设置查询参数,增强代码安全性。

本人微信公众号,欢迎扫码关注!

使用jdbc拼接条件查询语句时如何防止sql注入

  • 最近公司的项目在上线时需要进行安全扫描,但是有几个项目中含有部分老代码,操作数据库时使用的是jdbc,并且竟然好多都是拼接的SQL语句,真是令人抓狂。
  • 在具体改造时,必须使用PreparedStatement来防止SQL注入,普通SQL语句比较容易改造,本重点探讨在拼接查询条件的时候如何方式SQL注入,具体思路请参考下面的示例代码。

1 数据库示例数据

2 使用statement(不防止SQL注入)

2.1 示例代码
@Test
public void statementTest() {
    String username = "tom";
    String sex = "1";
    String address = "' or '1'='1";
    Statement stat = null;
    ResultSet res = null;

    Connection conn = ConnectionFactory.getConnection();
    String sql = "SELECT * FROM user WHERE 1 = 1";

    sql += username == null ? "" : " AND username = '" + username + "'";
    sql += sex == null ? "" : " AND sex = '" + sex + "'";
    sql += address == null ? "" : " AND address = '" + address + "'";

    System.out.println(sql);

    try {
        stat = conn.createStatement();
        res = stat.executeQuery(sql);
        printRes(res);
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        ResourceClose.close(res, stat, conn);
    }
}
2.2 控制台结果
SELECT * FROM user WHERE 1 = 1 AND username = 'tom' AND sex = '1' AND address = '' or '1'='1'
10 tom 2014-07-10 1 beijing 
16 tom 2018-07-31 1 shanghai 
22 tom 2019-04-16 2 shanghai 
24 tom 2019-06-22 1 guangzhou 
25 tom 2019-01-22 2 guangzhou 
28 tom 2018-07-31 1 shenzhen 
2.3 小结
  • 经过上面的示例代码我们可以发现,单纯拼接SQL语句是非常危险的,特别容易被SQL注入,但是如果使用prepareStatement的话,像这种条件查询我们预先并不能确定到底有多少个?(占位符),也就不能使用按照?(占位符)索引去设置参数了,那怎么办呢?
  • 别担心,此时我们使用一个小小的技巧,具体参考下面的示例代码

3 使用prepareStatement(可以防止SQL注入)

3.1 示例代码
@Test
public void prepareStatementTest() {
    String username = "tom";
    String sex = null;
    String address = "' or '1'='1";
    PreparedStatement stat = null;
    ResultSet res = null;

    Connection conn = ConnectionFactory.getConnection();
    String sql = "SELECT * FROM user WHERE 1 = 1";

    List<Object> param = new ArrayList<>();

    if (username != null) {
        sql += " AND username = ?";
        param.add(username);
    }
    if (sex != null) {
        sql += " AND sex = ?";
        param.add(sex);
    }
    if (address != null) {
        sql += " AND address = ?";
        param.add(address);
    }
    System.out.println(sql);

    try {
        stat = conn.prepareStatement(sql);
        for (int i = 0; i < param.size(); i++) {
            stat.setObject(i+1,param.get(i));
        }
        res = stat.executeQuery();
        printRes(res);
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        ResourceClose.close(res, stat, conn);
    }
}
3.2 控制台结果
SELECT * FROM user WHERE 1 = 1 AND username = ? AND address = ?
3.3 小结
  • 可以看出,使用prepareStatement是可以防止SQL注入的。
  • 但进行类似条件拼接这种操作时,可以先把参数放入一个集合中,然后遍历集合,同时利用setObject(index,obj)这个方法就可以动态的获取参数的索引了,而且不用关心参数是何种类型。

4 问题总结

  • 如今在进行项目开发时已经很少使用原生的jdbc了,大多数都用功能强大的框架去完成,他们帮我们简化了很多操作,如获取数据库连接、封装结果集、SQL预编译(可以防止SQL注入)
  • 如果实在避免不了使用的话一定要使用可以需编译的prepareStatement对象,避免被SQL注入带来的风险。
防止 SQL 注入是数据库安全的核心问题,尤其是在动态拼接 SQL 条件(如 `WHERE name = '...'`)。如果直接将用户输入拼接SQL 语句中,攻击者可以构造恶意输入来篡改查询逻辑,甚至删除数据、获取敏感信息。 --- ### ✅ 正确的防御方式:使用 **参数化查询(Prepared Statements)** 参数化查询不会将用户输入作为 SQL 代码的一部分执行,而是将其作为“数据”传递给预编译的 SQL 模板,从而彻底阻断 SQL 注入的可能性。 --- ### 🔧 示例:使用 Python + `mysql-connector-python` 防止 SQL 注入 ```python import mysql.connector # 连接数据库 conn = mysql.connector.connect( host='localhost', user='your_username', password='your_password', database='your_database' ) cursor = conn.cursor() # ❌ 危险!不要这样做(字符串拼接) name = input("请输入员工姓名: ") # 危险操作:容易被注入,例如输入 ' OR 1=1 -- query_bad = f"SELECT * FROM employee WHERE name = '{name}'" # cursor.execute(query_bad) # 绝对禁止! # ✅ 安全做法:使用参数化查询 query_good = "SELECT * FROM employee WHERE name = %s" cursor.execute(query_good, (name,)) # 获取结果 results = cursor.fetchall() for row in results: print(row) # 关闭连接 cursor.close() conn.close() ``` **解释:** - `%s` 是占位符(不是字符串格式化),由驱动程序负责安全转义。 - `(name,)` 是参数元组,即使包含特殊字符(如 `'` 或 `;`),也会被视为普通数据,不会改变 SQL 结构。 - 使用 `cursor.execute(query, params)` 而非字符串拼接。 --- ### 🛡️ 其他语言中的参数化查询示例 #### ✅ Java (JDBC) ```java String name = "John Doe"; String sql = "SELECT * FROM employee WHERE name = ?"; PreparedStatement pstmt = connection.prepareStatement(sql); pstmt.setString(1, name); ResultSet rs = pstmt.executeQuery(); ``` #### ✅ PHP (PDO) ```php $name = $_GET['name']; $stmt = $pdo->prepare("SELECT * FROM employee WHERE name = ?"); $stmt->execute([$name]); $results = $stmt->fetchAll(); ``` #### ✅ Node.js (使用 mysql2) ```javascript const [rows] = await connection.execute( 'SELECT * FROM employee WHERE name = ?', [name] ); ``` --- ### 🚫 常见错误:看似“转义”实则仍危险 有些人尝试手动转义引号,比如: ```python name = name.replace("'", "\\'") ``` 这是 **不可靠的**,因为: - 不同数据库的转义规则不同。 - 字符编码问题可能导致绕过。 - 复杂场景下极易遗漏。 ✅ 唯一可靠的方法是:**永远使用参数化查询**。 --- ### 📌 特别注意:以下操作无法用参数化?怎么办? | 场景 | 是否可用参数化 | 解决方案 | |------|----------------|----------| | `WHERE name = ?` | ✅ 可以 | 直接使用参数 | | `ORDER BY ?` | ❌ 不行(列名不能参数化) | 白名单校验 | | `LIMIT ?, ?` | ✅ 可以(数值参数) | 使用整数参数 | | `TABLE_NAME` 动态表名 | ❌ 不行 | 白名单 + 正则匹配 | #### 示例:安全处理动态排序字段 ```python allowed_columns = {'name', 'department', 'salary'} order_by = request.get('order_by') if order_by not in allowed_columns: raise ValueError("Invalid column") query = f"SELECT * FROM employee ORDER BY {order_by}" cursor.execute(query) ``` > 注意:这里使用了字符串拼接,但前提是 `order_by` 来自白名单,确保无害。 --- ### 🔐 额外安全建议 1. **最小权限原则**:数据库用户只赋予必要权限(如不用 root 账号运行应用)。 2. **输入验证**:对长度、类型、格式进行校验(如姓名不能含特殊符号)。 3. **日志审计**:记录敏感操作,便于追踪异常行为。 4. **使用 ORM 框架**(如 SQLAlchemy、Django ORM):它们默认使用参数化查询,减少手写 SQL 错误。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值