JDBC连接如何防止SQL注入?

1. 绪言

  • 想要学习mybatis的相关知识,学习之前复习了下JDBC的相关知识

  • 发现自己竟然连如何实现JDBC连接都不知道了,真的是少壮用CV(粘贴复制),老大徒伤悲😂

  • 总结一下,JDBC连接分为三步:

    • 加载JDBC驱动
    • 创建数据库连接
    • 操作数据库实现增删改查:获取statement,执行SQL语句,处理执行结果
  • 简单的连接和查询示例如下:

    import java.sql.*;
    
    public class Test {
        private static final String DRIVER = "com.mysql.jdbc.Driver";
        // 指定编码格式,解决无法匹配中文字符的问题
        private static final String URL = "jdbc:mysql://localhost:3306/lucy?useUnicode=TRUE&characterEncoding=UTF-8";
        private static final String USER = "root";
        private static final String PASSWORD = "123456";
        private static Connection conn = null;
        private static Statement stmt = null;
        private static ResultSet rs = null;
        private static PreparedStatement ptmt = null;
    
        public static void main(String[] args) {
            try {
                // 1. 加载驱动
                Class.forName(DRIVER);
                // 2. 创建数据库连接
                conn = DriverManager.getConnection(URL, USER, PASSWORD);
                // 3. 操作数据库,实现增删改查
                stmt = conn.createStatement();
                rs = stmt.executeQuery("select * from stu");
                // 遍历查询结果
                while (rs.next()) {
                    StringBuilder sb = new StringBuilder("[id=");
                    sb.append(rs.getLong("id"));
                    sb.append(", name=" + rs.getString("name"));
                    sb.append(", age=" + rs.getInt("age"));
                    sb.append(", class_id=" + rs.getLong("class_id"));
                    sb.append("]");
                    System.out.println(sb.toString());
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            } finally { // 关闭连接
                try {
                    if (rs != null) {
                        rs.close();
                    }
                    if (stmt != null) {
                        stmt.close();
                    }
                    if (ptmt != null) {
                        ptmt.close();
                    }
                    if (conn != null) {
                        conn.close();
                    }
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
    }
    
  • stu表的建表语句见博客:数据库中的四大join & 笛卡尔乘积(以MySQL为例)

2. SQL注入的问题

2.1 字符串拼接引发SQL注入

  • 新需求:这是一个简单的学生信息管理系统,需要通过学生姓名查询学生信息。

  • 详细设计:编写一个方法,入参为学生姓名,将入参拼接成完整的SQL语句

  • 代码实现:

    public static void queryWithParam(String name) throws SQLException {
        String sql = "select * from stu where name='" + name + "'";
        rs = stmt.executeQuery(sql);
        // 打印查询结果
        while (rs.next()) {
            StringBuilder sb = new StringBuilder("[id=");
            sb.append(rs.getLong("id"));
            sb.append(", name=" + rs.getString("name"));
            sb.append(", age=" + rs.getInt("age"));
            sb.append(", class_id=" + rs.getLong("class_id"));
            sb.append("]");
            System.out.println(sb.toString());
        }
    }
    
  • 如果用户传入的参数为lucy,此时SQL字符串为select * from stu where name='lucy',能查询到想要的数据

  • 如果这是一个急性子的用户,他不仅想要知道lucy的信息,还想要知道郭麒麟的信息,这时传入的参数为:lucy' or name='郭麒麟

  • 拼接出来的SQL语句为select * from stu where name='lucy' or name='郭麒麟',查询出来的学生信息就不止lucy一条信息了

  • 由此可见,这样的字符串拼接方式存在SQL注入的风险

2.2 字符串转义避免SQL注入

  • 上面的例子会可能发生SQL注入,那是因为传入的字符串在特定的环境下它不再是一个整体,而是可以拆分出多个查询条件
  • 为了避免SQL注入,最直接的想法就是让传入的参数始终是一个整体。
  • 我们可以对入参中的'字符进行转义\',这样就保证其整体性
  • 实践发现,lucy\' or name=\'郭麒麟简单的转义在字符串拼接时,会自动取消转义:select * from stu where name='lucy' or name='郭麒麟'
  • 因此,拼接而成的SQL语句任然有SQL注入的风险
  • 廖雪峰官网给出的转义方法

要避免SQL注入攻击,一个办法是针对所有字符串参数进行转义,但是转义很麻烦,而且需要在任何使用SQL的地方增加转义代码。

  • 看起来,通过转义避免SQL注入是很不现实的

2.3 使用PreparedStatement避免SQL注入

  • 可以使用PreparedStatement来实现数据库操作,它的特点是:使用占位符?表示SQL语句中的参数,通过set方法为SQL语句传入参数

  • 简单示例如下:

    public static void queryPrepare(String name) throws SQLException {
        // 预编译SQL,避免
        ptmt = conn.prepareStatement("select * from stu where name=?");
        ptmt.setString(1, name);
        // 打印预编译后的SQL语句
        System.out.println(ptmt.toString());
        rs = ptmt.executeQuery();
        while (rs.next()) {
            StringBuilder sb = new StringBuilder("[id=");
            sb.append(rs.getLong("id"));
            sb.append(", name=" + rs.getString("name"));
            sb.append(", age=" + rs.getInt("age"));
            sb.append(", class_id=" + rs.getLong("class_id"));
            sb.append("]");
            System.out.println(sb.toString());
        }
    }
    
  • 这里,我们仍然传入之前成功发生SQL注入的参数lucy' or name='郭麒麟,发现打印出来的SQL语句为:

    select * from stu where name='lucy\' or name=\'郭麒麟'
    
  • 神奇之处:预编译自动将SQL语句进行了转义,使得传入的参数成为了一个整体

  • 也就是说,这里的查询条件变成了lucy\' or name=\'郭麒麟,SQL语句无法被截断了

3. 总结

使用PreparedStatement的优点:

  • 安全性不同: PreparedStatement可以有效防止sql注入,而Statment不能防止sql注入。
  • 语法不同:PreparedStatement可以使用预编译的sql,而Statment只能使用静态的sql
  • 效率不同:对于更改参数的同一SQL语句,PreparedStatement可以使用sql缓存区,效率比Statment
  • 0
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JDBC (Java Database Connectivity) 是一种用于Java应用程序与关系数据库交互的API标准。要使用JDBC连接SQL Server 2008,首先你需要确保你的项目中已经包含了相应的JDBC驱动,比如Microsoft JDBC Driver for SQL Server (msjdbcsql.jar)。 以下是使用JDBC连接SQL Server 2008的基本步骤: 1. **添加JDBC驱动**: 在你的项目构建路径中添加MS JDBC驱动库。如果是Maven项目,可以在pom.xml文件中添加依赖: ```xml <dependency> <groupId>com.microsoft.sqlserver</groupId> <artifactId>mssql-jdbc</artifactId> <version>9.4.x</version> <!-- 更新到最新的版本 --> </dependency> ``` 2. **加载驱动**: 在Java代码中,使用Class.forName()方法加载驱动: ```java Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); ``` 3. **创建连接**: 使用DriverManager.getConnection()方法创建到数据库的连接,提供数据库URL、用户名和密码: ```java String url = "jdbc:sqlserver://your_server_address;databaseName=your_database_name"; String user = "your_username"; String password = "your_password"; Connection conn = DriverManager.getConnection(url, user, password); ``` 4. **执行SQL查询或操作**: 使用Connection对象的Statement或PreparedStatement接口执行SQL语句: ```java Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT * FROM your_table"); // 或者使用预编译的Statement(防止SQL注入) String sql = "SELECT * FROM your_table WHERE id = ?"; PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setInt(1, your_id); rs = pstmt.executeQuery(); ``` 5. **处理结果集**: 处理查询结果,例如遍历ResultSet,获取数据。 6. **关闭资源**: 用完后记得关闭Connection、Statement和ResultSet: ```java rs.close(); pstmt.close(); stmt.close(); conn.close(); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值