JDBC之【SQL注入】

sql注入原理

用户输入的信息中含有sql语句的关键字,并且这些关键字参与sql语句编译过程,导致sql语句的原意被扭曲,进而达到sql注入。

sql注入的例子

存在SQL注入的代码:

public class Test {
    public static void main(String[] args) {
        // 初始化一个界面
        Map<String,String> userLoginInfo = initUI();
        // 验证用户名和密码
        boolean loginSuccess = login(userLoginInfo);
        // 最后输出结果
        System.out.println(loginSuccess ? "登录成功" : "登录失败");
    }

    /**
     * 用户登录
     * @param userLoginInfo 用户登录信息
     * @return false表示登录失败,true表示登录成功
     */
    private static boolean login(Map<String, String> userLoginInfo) {
        // JDBC
        boolean loginSuccess = false;
        Connection connection = null;
        Statement statement = null;
        ResultSet rs = null;
        String loginName = userLoginInfo.get("loginName");
        String loginPwd = userLoginInfo.get("loginPwd");

        try {
            // 1、注册驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            // 2、获取连接
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/MyTest","root","******");
            // 3、获取数据库操作对象
            statement = connection.createStatement();
            // 4、执行sql语句
            String sql = "select * from tbl_user where loginName = '" + loginName + "' and loginPwd = '" + loginPwd + "'";
            rs = statement.executeQuery(sql);
            // 5、处理结果集
            if(rs.next()){
                loginSuccess = true;
            }
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        } finally {
            // 6、释放资源
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (statement != null) {
                try {
                    statement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        return loginSuccess;
    }

    /**
     * 初始化用户界面
     * @return 用户输入的用户名和密码等信息
     */
    private static Map<String, String> initUI() {
        Scanner s = new Scanner(System.in);
        System.out.print("用户名:");
        String loginName = s.nextLine();
        System.out.print("密码:");
        String loginPwd = s.nextLine();
        Map<String,String> userLoginInfo = new HashMap<>();
        userLoginInfo.put("loginName", loginName);
        userLoginInfo.put("loginPwd", loginPwd);
        return userLoginInfo;
    }
}

当输入如下时,密码即使不正确,也能登录成功: 

 

 可以通过断点查看,发现实际sql语句中多出了 or '1'='1',导致这条sql语句永远能查到数据

sql注入的解决方法 

只要用户提供的信息不参与SQL语句的编译过程,问题就解决了。要想用户提供的信息不参与SQL语句的编译,那么必须使用java.sql.PreparedStatement

PreparedStatement接口继承了java.sql.Statement

PreparedStatement是属于预编译的数据库操作对象

PreparedStatement的原理是:预先对SQL语句的框架进行编译,然后再给SQL语句传“值”

使用方法:

String sql = "select * from tbl_user where loginName = ? and loginPwd = ?";
ps = connection.prepareStatement(sql);

注:

(1)SQL语句的框子,其中一个?,表示一个占位符,一个?将来接受一个“值”,注意:占位符不能用单引号括起来。

(2)给占位符?传值(第一个问号下标是1,第二个问号下标是2,JDBC所有下标从1开始)

setString(占位符下标,String)

通过PreparedStatement接口改良上方代码

解决了SQL注入的问题

public static void main(String[] args) {
        // 初始化一个界面
        Map<String,String> userLoginInfo = initUI();
        // 验证用户名和密码
        boolean loginSuccess = login(userLoginInfo);
        // 最后输出结果
        System.out.println(loginSuccess ? "登录成功" : "登录失败");
    }

    /**
     * 用户登录
     * @param userLoginInfo 用户登录信息
     * @return false表示登录失败,true表示登录成功
     */
    private static boolean login(Map<String, String> userLoginInfo) {
        // JDBC
        boolean loginSuccess = false;
        Connection connection = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        String loginName = userLoginInfo.get("loginName");
        String loginPwd = userLoginInfo.get("loginPwd");

        try {
            // 1、注册驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            // 2、获取连接
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/MyTest","root","jht123456");
            // 3、获取预编译的数据库操作对象
            // ?表示占位符,不能写成'?',否则编译器认为是普通的字符串
            String sql = "select * from tbl_user where loginName = ? and loginPwd = ?";
            ps = connection.prepareStatement(sql);
            // 给占位符?传值(第一个问号下标是1,第二个问号下标是2,JDBC所有下标从1开始)
            ps.setString(1,loginName);
            ps.setString(2,loginPwd);
            // 4、执行sql语句
            rs = ps.executeQuery();
            // 5、处理结果集
            if(rs.next()){
                loginSuccess = true;
            }
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        } finally {
            // 6、释放资源
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (ps != null) {
                try {
                    ps.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        return loginSuccess;
    }

    /**
     * 初始化用户界面
     * @return 用户输入的用户名和密码等信息
     */
    private static Map<String, String> initUI() {
        Scanner s = new Scanner(System.in);
        System.out.print("用户名:");
        String loginName = s.nextLine();
        System.out.print("密码:");
        String loginPwd = s.nextLine();
        Map<String,String> userLoginInfo = new HashMap<>();
        userLoginInfo.put("loginName", loginName);
        userLoginInfo.put("loginPwd", loginPwd);
        return userLoginInfo;
    }

Statement和PreparedStatement的对比

1、Statement会存在SQL注入问题,PreparedStatement解决了这个问题。

2、Statement是编译一次执行一次。PreparedStatement是编译一次,可执行N次。相比之下PreparedStatement效率较高。

3、PreparedStatement会在编译阶段进行类型的安全检查。

4、综上所述,PreparedStatement使用较多,只有极少数的情况下需要使用Statement,只有必须要求SQL注入的情况下,才使用Statement。

必须使用Statement的情况

当sql语句中必须用到字符串拼接时,则必须使用Statement

public static void main(String[] args) {
        Scanner s = new Scanner(System.in);
        System.out.print("升序输入asc,降序输入desc:");
        String order = s.nextLine();
        // 定义变量
        Connection connection = null;
        Statement statement = null;
        ResultSet rs = null;


        try {
            // 注册驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            // 获取连接
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/MyTest","root", "********");
            // 获取数据库操作对象
            statement = connection.createStatement();
            // 执行sql
            String sql = "select * from emp order by sal " + order;
            rs = statement.executeQuery(sql);
            // 处理查询结果集
            while(rs.next()){
                String ename = rs.getString("ename");
                double sal = rs.getDouble("sal");
                System.out.println("姓名:" + ename + ",薪资:" + sal);
            }
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        } finally {
            // 释放资源
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (statement != null) {
                try {
                    statement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值