跳至主要內容

javaweb - JAVA与数据库3

codejavawebmysql约 1291 字大约 4 分钟

JDBC

实现登陆与SQL注入攻击

在使用之前,我们先来看看如果我们想模拟登陆一个用户,我们该怎么去写:

try (Connection connection = DriverManager.getConnection("URL","用户名","密码");
     Statement statement = connection.createStatement();
     Scanner scanner = new Scanner(System.in)){
    ResultSet res = statement.executeQuery("select * from user where username='"+scanner.nextLine()+"'and pwd='"+scanner.nextLine()+"';");
    while (res.next()){
        String username = res.getString(1);
        System.out.println(username+" 登陆成功!");
    }
}catch (SQLException e){
    e.printStackTrace();
}

用户可以通过自己输入用户名和密码来登陆,乍一看好像没啥问题,那如果我输入的是以下内容呢:

Test
1111' or 1=1; -- 
# Test 登陆成功!

1=1一定是true,那么我们原本的SQL语句会变为:

select * from user where username='Test' and pwd='1111' or 1=1; -- '

我们发现,如果允许这样的数据插入,那么我们原有的SQL语句结构就遭到了破坏,使得用户能够随意登陆别人的账号。因此我们可能需要限制用户的输入来防止用户输入一些SQL语句关键字,但是关键字非常多,这并不是解决问题的最好办法。

使用PreparedStatement

我们发现,如果单纯地使用Statement来执行SQL命令,会存在严重的SQL注入攻击漏洞!而这种问题,我们可以使用PreparedStatement来解决:

public static void main(String[] args) throws ClassNotFoundException {
    try (Connection connection = DriverManager.getConnection("URL","用户名","密码");
         PreparedStatement statement = connection.prepareStatement("select * from user where username= ? and pwd=?;");
         Scanner scanner = new Scanner(System.in)){

        statement.setString(1, scanner.nextLine());
        statement.setString(2, scanner.nextLine());
        System.out.println(statement);    //打印查看一下最终执行的
        ResultSet res = statement.executeQuery();
        while (res.next()){
            String username = res.getString(1);
            System.out.println(username+" 登陆成功!");
        }
    }catch (SQLException e){
        e.printStackTrace();
    }
}

我们发现,我们需要提前给到PreparedStatement一个SQL语句,并且使用?作为占位符,它会预编译一个SQL语句,通过直接将我们的内容进行替换的方式来填写数据。使用这种方式,我们之前的例子就失效了!我们来看看实际执行的SQL语句是什么:

com.mysql.cj.jdbc.ClientPreparedStatement: select * from user where username= 'Test' and pwd='123456'' or 1=1; -- ';

我们发现,我们输入的参数一旦出现'时,会被变为转义形式\',而最外层有一个真正的'来将我们输入的内容进行包裹,因此它能够有效地防止SQL注入攻击!

管理事务

JDBC默认的事务处理行为是自动提交,所以前面我们执行一个SQL语句就会被直接提交(相当于没有启动事务),所以JDBC需要进行事务管理时,首先要通过Connection对象调用setAutoCommit(false)方法, 将SQL语句的提交(commit)由驱动程序转交给应用程序负责。

con.setAutoCommit();   //关闭自动提交后相当于开启事务。
// SQL语句
// SQL语句
// SQL语句
con.commit();或 con.rollback();

一旦关闭自动提交,那么现在执行所有的操作如果在最后不进行commit()来提交事务的话,那么所有的操作都会丢失,只有提交之后,所有的操作才会被保存!也可以使用rollback()来手动回滚之前的全部操作!

public static void main(String[] args) throws ClassNotFoundException {
    try (Connection connection = DriverManager.getConnection("URL","用户名","密码");
         Statement statement = connection.createStatement()){

        connection.setAutoCommit(false);  //关闭自动提交,现在将变为我们手动提交
        statement.executeUpdate("insert into user values ('a', 1234)");
        statement.executeUpdate("insert into user values ('b', 1234)");
        statement.executeUpdate("insert into user values ('c', 1234)");

        connection.commit();   //如果前面任何操作出现异常,将不会执行commit(),之前的操作也就不会生效
    }catch (SQLException e){
        e.printStackTrace();
    }
}

我们来接着尝试一下使用回滚操作:

public static void main(String[] args) throws ClassNotFoundException {
    try (Connection connection = DriverManager.getConnection("URL","用户名","密码");
         Statement statement = connection.createStatement()){

        connection.setAutoCommit(false);  //关闭自动提交,现在将变为我们手动提交
        statement.executeUpdate("insert into user values ('a', 1234)");
        statement.executeUpdate("insert into user values ('b', 1234)");

        connection.rollback();   //回滚,撤销前面全部操作

        statement.executeUpdate("insert into user values ('c', 1234)");

        connection.commit();   //提交事务(注意,回滚之前的内容都没了)

    }catch (SQLException e){
        e.printStackTrace();
    }
}

同样的,我们也可以去创建一个回滚点来实现定点回滚:

public static void main(String[] args) throws ClassNotFoundException {
    try (Connection connection = DriverManager.getConnection("URL","用户名","密码");
         Statement statement = connection.createStatement()){

        connection.setAutoCommit(false);  //关闭自动提交,现在将变为我们手动提交
        statement.executeUpdate("insert into user values ('a', 1234)");
        
        Savepoint savepoint = connection.setSavepoint();   //创建回滚点
        statement.executeUpdate("insert into user values ('b', 1234)");

        connection.rollback(savepoint);   //回滚到回滚点,撤销前面全部操作

        statement.executeUpdate("insert into user values ('c', 1234)");

        connection.commit();   //提交事务(注意,回滚之前的内容都没了)

    }catch (SQLException e){
        e.printStackTrace();
    }
}

通过开启事务,我们就可以更加谨慎地进行一些操作了,如果我们想从事务模式切换为原有的自动提交模式,我们可以直接将其设置回去:

public static void main(String[] args) throws ClassNotFoundException {
    try (Connection connection = DriverManager.getConnection("URL","用户名","密码");
         Statement statement = connection.createStatement()){

        connection.setAutoCommit(false);  //关闭自动提交,现在将变为我们手动提交
        statement.executeUpdate("insert into user values ('a', 1234)");
        connection.setAutoCommit(true);   //重新开启自动提交,开启时把之前的事务模式下的内容给提交了
        statement.executeUpdate("insert into user values ('d', 1234)");
        //没有commit也成功了!
    }catch (SQLException e){
        e.printStackTrace();
    }
}
上次编辑于: