SQL注入问题的解决(PreparedStatement)

sql注入的问题

上篇博客用户登录业务简易实现结尾提到了代码的不足:存在SQL注入问题。

下面演示一下什么是SQL,注入问题。(就拿上篇的代码举个例子)

SQL注入问题的解决(PreparedStatement)_第1张图片
SQL注入问题的解决(PreparedStatement)_第2张图片

以上为正常现象,但有些“不法分子”抓住了代码的漏洞,干了一些不为人知的事情。

SQL注入问题的解决(PreparedStatement)_第3张图片

SQL注入问题的解决(PreparedStatement)_第4张图片

What!竟然登录成功了!发生了甚么?让我们Debug一下,一探究竟。

SQL注入问题的解决(PreparedStatement)_第5张图片

select * from t_user where username = ‘jiuzhe’ and userpwd = ‘jiuzhe’ or ‘1’=‘1’

‘1’='1’为true,从而导致了整个柿子返回结果为true。

SQL注入问题的解决(PreparedStatement)_第6张图片

如何解决sql注入的问题(PreparedStatement)

导致SQL注入的根本原因是什么?

​ 用户输入的信息中含有SQL语句的关键字,最为致命的是这些关键字参与了SQL语句的编译过程

​ 导致SQL语句的原意被扭曲,进而达到SQL注入。

如何解决SQL注入的问题?

​ 只要用户输入的信息不参与SQL语句的编译过程,问题就解决了。

​ 即使用户输入的信息含有SQL语句的关键字,但由于不参与SQL语句的编译过程,那么也起不到作用。

​ 为此,我们要使用Java.sql.PreparedStatement。

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

修改后的代码:

public class userLogin2 {
    public static void main(String[] args) {
        // 初始化界面,返回用户的信息。
        Map<String, String> userLoginInfo = initUI();
        // 验证用户名和密码
        boolean loginSuccess = login(userLoginInfo);
        System.out.println(loginSuccess ? "Success" : "False");
    }

    private static boolean login(Map<String, String> userLoginInfo) {
        // 打一个标记
        boolean loginSuccess = false;

        String loginName = userLoginInfo.get("userLoginName");
        String loginPwd = userLoginInfo.get("userLoginPwd");

        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;

        try {
            // 1.注册驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            // 2.获取连接
            conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/userdata",
                    "root", "root");
            // 3.获取预编译的数据库操作对象
            // SQL语句的框架,其中一个?,表示一个占位符,一个?将来接收一个“值”,注意占位符不能用单引号括起来。
            String sql = "select * from t_user where username = ? and userpwd = ?";// sql语句的框架
            // 程序运行到此处,会发送sql语句框架传给DBMS,然后DBMS对sql语句进行预编译。
            ps = conn.prepareStatement(sql);
            // 给占位符?传值(第一个?下标为1,第二个?下表为2。。。。。。)
            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 (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        return loginSuccess;
    }

    private static Map<String, String> initUI() {
        // 获取用户信息
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入账号:");
        String userLoginName = sc.nextLine();
        System.out.println("请输入密码:");
        String userLoginPwd = sc.nextLine();
        Map<String, String> userLoginInfo = new HashMap<>();
        // 将用户信息存入Map中
        userLoginInfo.put("userLoginName", userLoginName);
        userLoginInfo.put("userLoginPwd", userLoginPwd);
        return userLoginInfo;
    }
}

SQL注入问题的解决(PreparedStatement)_第7张图片

SQL注入问题的解决(PreparedStatement)_第8张图片

SQL注入问题的解决(PreparedStatement)_第9张图片

对于不法分子,我想说:就这?

SQL注入问题的解决(PreparedStatement)_第10张图片

Statement和PreparedStatement的对比

  • Statement存在SQL注入问题,PreparedStatement解决了SQL注入问题。
  • Statement是编译一次执行一次,而PreparedStatement是编译一次,可执行N次,效率较高一些。
  • PreparedStatement在编译过程中进行类型的安全检查。

我们以后大部分情况下,都会使用PreparedStatement。

只有在业务方面必须支持SQL注入,进行SQL语句拼接的时候,必须使用Statement。

你可能感兴趣的:(JDBC,leetcode,算法,职场和发展)