首先我們模擬下簡單的登錄功能:
簡單需求:
模擬用戶登錄的簡單實現
業務描述:
程序運行 的時候,提供一個輸入的人口,可以使用輸入用戶名個密碼,校驗用戶名和密碼是否合法,數據庫中是否存在用戶
合法登錄成功,否則注冊登錄
數據准備:
設計數據庫表,通常我們使用建模工具:PowerDesigner
接下來上代碼:
db.properties配置文件如下:
driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/user user=root password=guisha
實現如下:
package com.guisha.JDBC.Logn; import java.io.IOException; import java.io.InputStream; import java.sql.*; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.Scanner; public class JDBCTest { public static void main(String[] agre) { //獲取登錄時的用戶和密碼 Map<String, String> loginInfo = loginInfo(); //獲取用戶名登錄驗證 boolean loginSuccess = login(loginInfo); //輸出結果驗證 System.out.println(loginSuccess ? "登錄成功!" : "請注冊登錄!"); } private static boolean login(Map<String, String> loginInfo) { //登錄標記 boolean flag = false; //定義用戶名和登錄密碼 String loginName = loginInfo.get("loginName"); String loginPwa = loginInfo.get("loginPwa"); //使用的接口 Connection conn = null; Statement stan = null; ResultSet result = null; try { InputStream input = JDBCTest.class.getClassLoader().getResourceAsStream("db.properties"); Properties property = new Properties(); try { //裝載配置文件 property.load(input); String driver = property.getProperty("driver"); String url = property.getProperty("url"); String user = property.getProperty("user"); String password = property.getProperty("password"); //注冊驅動 Class.forName(driver); //從輸入字節流中讀取屬性列表(鍵和元素對)。輸入流采用load(Reader)中指定的簡單的面向行的格式,並假定使用ISO 8859-1字符編碼。 try { //創建鏈接 conn = DriverManager.getConnection(url, user, password); //獲取數據庫操作對象 stan = conn.createStatement(); //執行Sql,獲取數據 String sql = "select * from loginname where NAME='" + loginName + "' and NAMEPASS= '" + loginPwa + "' "; // 查詢結果集 result = stan.executeQuery(sql); //判斷 if (result.next()){ flag = true; } } catch (SQLException e) { e.printStackTrace(); } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }finally { //釋放資源 if (result != null){ try { result.close(); } catch (SQLException e) { e.printStackTrace(); } } //釋放資源 if (stan != null){ try { stan.close(); } catch (SQLException e) { e.printStackTrace(); } } //釋放資源 if (conn != null){ try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } //返回標識 return flag; } public static Map<String, String> loginInfo() { //接受鍵盤輸入 Scanner scanner = new Scanner(System.in); //輸入用戶名 System.out.println("請輸入用戶名:"); String loginName = scanner.nextLine(); //輸入密碼 System.out.println("請輸入密碼:"); String loginPwa = scanner.nextLine(); //創建鍵值對集合 Map<String, String> map = new HashMap<String, String>(); //將數據添加到map集合對象中 map.put("loginName", loginName); map.put("loginPwa", loginPwa); //返回集合對象 return map; } }
我們需要了解SQL注入
以上代碼中執行執行SQL部分,這里完成了SQL語句的拼接,而executeQuery這里是將sql語句發送給DBMS進行代碼編譯
正好將用戶提供的"非法信息"編譯進去,導致原SQL語句的含義被扭曲,會導不正常的操作變的正常、
導致SQL注入的根本原因是什么?
用戶輸入的信息中包含SQL語句的關鍵字,並且這些關鍵字參與sql語句的編譯過程,導致SQL語句原意改變,進而達到SQL注入的效果(不能小視,可能會造成數據庫崩潰或者被改變)
解決SQL注入問題?
首先我們知道出現SQL注入的原因是用戶在提供的信息包含SQL語句的關鍵字,並且在傳給DBMS預編譯時傳入了非法的SQL信息,那么,只要用戶提供的信息不參與SQL語句的編譯過程,就可以解決問題(即使信息中包含SQL語句的關鍵字,但是不參與預編譯,不起作用)。
想要用戶信息不參與SQL預編譯,就需要使用java.sql.PreparedStatement接口,此接口繼承了java.sql.Statement。
PreparedStatement是屬於預編譯的數據庫操作對象,原理是:預先對SQL語句的框架進行預編譯,然后再給SQL語句傳值
我們對代碼進行修改:
package com.guisha.JDBC.Logn; import java.io.IOException; import java.io.InputStream; import java.sql.*; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.Scanner; public class JDBCTest { public static void main(String[] agre) { //獲取登錄時的用戶和密碼 Map<String, String> loginInfo = loginInfo(); //獲取用戶名登錄驗證 boolean loginSuccess = login(loginInfo); //輸出結果驗證 System.out.println(loginSuccess ? "登錄成功!" : "請注冊登錄!"); } private static boolean login(Map<String, String> loginInfo) { //登錄標記 boolean flag = false; //定義用戶名和登錄密碼 String loginName = loginInfo.get("loginName"); String loginPwa = loginInfo.get("loginPwa"); //使用的接口 Connection conn = null; PreparedStatement ps = null; ResultSet result = null; try { InputStream input = JDBCTest.class.getClassLoader().getResourceAsStream("db.properties"); Properties property = new Properties(); try { //裝載配置文件 property.load(input); String driver = property.getProperty("driver"); String url = property.getProperty("url"); String user = property.getProperty("user"); String password = property.getProperty("password"); //注冊驅動 Class.forName(driver); //從輸入字節流中讀取屬性列表(鍵和元素對)。輸入流采用load(Reader)中指定的簡單的面向行的格式,並假定使用ISO 8859-1字符編碼。 try { //創建鏈接 conn = DriverManager.getConnection(url, user, password); //獲取數據庫操作對象 //SQL語句,其中一個 ? ,表示一個占位符,一個?將來接受一個“值”,注意:占位符不能使用 ' ' 括起來 String sql = "select * from loginname where NAME= ? and NAMEPASS = ? "; ps= conn.prepareStatement(sql); //給占位符串 “值” (第一個? 下標為1,第二個為2,切記 JDBC中所有的下標從1開始) ps.setString(1,loginName); ps.setString(2,loginPwa); //執行Sql,獲取數據對語句進行預編譯 // 獲取結果集 result = ps.executeQuery(); //判斷 if (result.next()){ flag = true; } } catch (SQLException e) { e.printStackTrace(); } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }finally { //釋放資源 if (result != null){ try { result.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 flag; } public static Map<String, String> loginInfo() { //接受鍵盤輸入 Scanner scanner = new Scanner(System.in); //輸入用戶名 System.out.println("請輸入用戶名:"); String loginName = scanner.nextLine(); //輸入密碼 System.out.println("請輸入密碼:"); String loginPwa = scanner.nextLine(); //創建鍵值對集合 Map<String, String> map = new HashMap<String, String>(); //將數據添加到map集合對象中 map.put("loginName", loginName); map.put("loginPwa", loginPwa); //返回集合對象 return map; } }