1. 程式人生 > 資料庫 >SQL注入問題並解決

SQL注入問題並解決

SQL注入問題

package com.etc;

import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

/*

    實現功能
        1.需求:模擬使用者登入實現功能
        2.業務描述:
            程式執行的時候 提供一個輸入的入口 可以讓使用者輸入使用者名稱和密碼
            使用者輸入使用者名稱和密碼之後  提交資訊 java程式收集到使用者資訊
            Java程式連線資料庫驗證使用者名稱和密碼是否合法
            合法:顯示登陸成功
            不合法:顯示登陸失敗
        3.資料的準備:
            在實際開發中 表的設計會使用一個專業的建模工具 PowerDesigner
            使用PD工具進行資料庫表的設計
        4.當前程式存在的問題:
            請輸入使用者:zhangsan
            請輸入密碼:zhangsan'or'1=1
            登入成功
            這種現象被稱為sql注入
        5.導致SQL注入的根本原因
            使用者輸入的資訊中含有sql語句的關鍵字  並且這些關鍵字參與sql語句的編譯過程
            導致sql語句的原意被扭曲  進而達到sql注入
 */
public class JdbcTest03{
    public static void main(String[] args) {
        // 初始化介面
        Map<String,String> userLoginInfo = initUI();
        // 驗證使用者名稱和密碼
        boolean loginSuccess = login(userLoginInfo);
        // 輸出最後結果
        System.out.println(loginSuccess ? "登入成功" : "登入失敗");
    }

    /**
     * 使用者登入
     * @param userLoginInfo 使用者登入資訊
     * @return true表示登入成功,false表示登入失敗
     */
    @SuppressWarnings("all")
    private static boolean login(Map<String, String> userLoginInfo) {
        //打標機
        boolean loginSuccess = false;

        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        try {
            // 1、註冊驅動
            Class.forName("com.mysql.jdbc.Driver");
            // 2、獲取連線
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3307/test","root","root");
            // 3、獲取資料庫操作物件
            stmt = conn.createStatement();
            // 4、執行sql語句
            String sql = "select * from t_user where loginName = '"+ userLoginInfo.get("userName")+ "' and loginPwd = '" + userLoginInfo.get("userPassword")+ "'";
            //以上正好完成了sql語句的拼接 以下程式碼的含義是 傳送sql語句給DBMS  DBMS進行sql編譯
            //正好將使用者提供的‘非法資訊’編譯進去 導致了sql語句含義被扭曲
            rs = stmt.executeQuery(sql);
            // 5、處理結果集
            if(rs.next()) {
                loginSuccess = true;
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            // 6、釋放資源
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
        return loginSuccess;
    }


    /**
     * 初試化介面
     * @return 使用者輸入的使用者名稱和密碼等登入資訊
     */
    private static Map<String, String> initUI() {
        Scanner s = new Scanner(System.in);

        System.out.print("請輸入使用者:");
        String userName = s.nextLine();
        System.out.print("請輸入密碼:");
        String userPassword = s.nextLine();

        Map<String,String> userLoginInfo = new HashMap<>();
        userLoginInfo.put("userName",userName);
        userLoginInfo.put("userPassword",userPassword);

        return userLoginInfo;
    }
}

解決sql注入問題

package com.etc;

import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

/*
    1.解決SQL注入問題:
        只要使用者提供的資訊不參與sql語句的編譯過程  問題就解決了
        要想使用者資訊不參與SQL語句的編譯 那麼必須使用java.sql.PerparedStatement
        PerparedStatement介面繼承了java.sql.Statement
        PerparedStatement是屬於預編譯的資料庫操作物件
        PerparedStatement的原理是 預先對SQL語句的框架進行編譯 然後再給SQL語句傳‘值’
     2.測試
           請輸入使用者:chen
           請輸入密碼:chen'or'1=1
           登入失敗
     3.解決sql注入的關鍵是
        使用者提供的資訊中即使含有sql語句的關鍵字 但是這些關鍵字並沒有參與編譯 不起作用

 */
public class JdbcTest05 {
    public static void main(String[] args) {
        // 初始化介面
        Map<String,String> userLoginInfo = initUI();
        // 驗證使用者名稱和密碼
        boolean loginSuccess = login(userLoginInfo);
        // 輸出最後結果
        System.out.println(loginSuccess ? "登入成功" : "登入失敗");
    }

    /**
     * 使用者登入
     * @param userLoginInfo 使用者登入資訊
     * @return true表示登入成功,false表示登入失敗
     */
    @SuppressWarnings("all")
    private static boolean login(Map<String, String> userLoginInfo) {
        //打標機
        boolean loginSuccess = false;
        String loginName = userLoginInfo.get("loginName");
        String loginPwd = userLoginInfo.get("loginPwd");
        Connection conn = null;
        PreparedStatement ps = null;//這裡使用PerparedStatement(預編譯的資料庫操作物件)
        ResultSet rs = null;
        try {
            // 1、註冊驅動
            Class.forName("com.mysql.jdbc.Driver");
            // 2、獲取連線
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3307/test","root","root");
            // 3、獲取資料庫操作物件
            //其中一個問號代表一個佔位符 一個問號將來接收一個“值”,注意:佔位符不能使用單引號括起來
            String sql = "select * from t_user where loginName = ? and loginPwd = ?";//SQL語句的框架
            //程式執行到此處 會發送sql語句框子給DBMS  然後DBMS進行sql語句的預先編譯
            ps = conn.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 e) {
            e.printStackTrace();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            // 6、釋放資源
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            if (ps != null) {
                try {
                    ps.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
        return loginSuccess;
    }


    /**
     * 初試化介面
     * @return 使用者輸入的使用者名稱和密碼等登入資訊
     */
    @SuppressWarnings("all")
    private static Map<String, String> initUI() {
        Scanner s = new Scanner(System.in);

        System.out.print("請輸入使用者:");
        String userName = s.nextLine();
        System.out.print("請輸入密碼:");
        String userPassword = s.nextLine();

        Map<String,String> userLoginInfo = new HashMap<>();
        userLoginInfo.put("loginName",userName);
        userLoginInfo.put("loginPwd",userPassword);

        return userLoginInfo;
    }
}

對比Statement和preparedStatement

4.對比Statement和PreparedStatement
   - Statementz存在注入問題  PreparedStatement解決了注入問題
   - Statement是編譯一次執行一次 PreparedStatement是編譯一次 可執行N次  PreparedStatement效率較高一些
   - preparedStatement會在編譯階段做型別的安全檢查
5.什麼情況下必須使用Statement?
   業務方面要求必須支援SQL注入現象
   Statement支援SQL注入 凡是業務方面要求是需要進行sql語句拼接的  必須使用Statement