什么是SQL注入
现象:
账户名:xxx
密码:xxx ’ or ’ 1 ’ = ’ 1
登录成功
原因:
原执行sql:
String sql = "select * from t_user where loginName = '"+loginName+"' and'"+loginPwd+"'";
注入后执行sql:
String sql = "select * from t_user where loginName = 'xxx' and loginPwd = 'xxx' or '1'='1'";
根本原因:
用户输入的信息中含有sql语句中的关键字,并且这些关键字参与sql语句的编译过程。
导致sql语句的原意被扭曲,进而达到sql注入。
解决方法
只要用户提供的信息不参与SQL语句的编译过程,问题就解决了。
即使用户提供的信息中含有SQL语句的关键字,但是没有参与编译,不起作用。
要想用户信息不参与SQL语句的编译,那么可以使用java . sql. PreparedStatement
PreparedStatement接口继承了java. sqI . Statement
PreparedStatement是属于预编译的数据库操作对象。
PreparedStatement的原理是:预先对SQL语句的框架进行编译,然后再给SQL语句传"值”。
原代码:
import java.sql.*; import java.util.*; /** * @Author: Mo * @Date: 2021/3/20 9:56 */ public class JDBCtest_6 { 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) { //打标记的意识 boolean loginSuccess = false; //单独定义变量 String loginName = userLoginInfo.get("loginName"); String loginPwd = userLoginInfo.get("loginPwd"); //JDBC代码 Connection conn = null; Statement stmt = null; ResultSet rs = null; try { //1.注册驱动 Class.forName("com.mysql.jdbc.Driver"); //2.获取连接 conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode","root","123456"); //3.获取数据库操作对象 stmt = conn.createStatement(); //4.执行sql String sql = "select * from t_user where loginName = '"+loginName+"' and '"+loginPwd+"'"; rs = stmt.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 (stmt != null){ try { stmt.close(); }catch (SQLException e){ e.printStackTrace(); } } if (conn != null){ try { conn.close(); }catch (SQLException e){ e.printStackTrace(); } } } return loginSuccess; } /** *初始化用户界面 *return用户输入的用户名和密码等登录信息 */ private static Map<String,String> initUI() { Scanner s = new Scanner(System.in); System.out.println("用户名:"); String loginName = s.nextLine(); System.out.println("密码:"); String loginPwd = s.nextLine(); Map<String,String>userLoginInfo = new HashMap<>(); userLoginInfo.put("loginName",loginName); userLoginInfo.put("loginPwd",loginPwd); return userLoginInfo; } }
修改后:
import java.sql.*; import java.util.HashMap; import java.util.Map; import java.util.Scanner; /** * @Author: Mo * @Date: 2021/3/20 19:37 */ public class JDBCtest_7 { 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) { //打标记的意识 boolean loginSuccess = false; //单独定义变量 String loginName = userLoginInfo.get("loginName"); String loginPwd = userLoginInfo.get("loginPwd"); //JDBC代码 Connection conn = null; PreparedStatement ps = null;//这里使用PreparedStatement(预编译的数据库操作对象) ResultSet rs = null; try { //1.注册驱动 Class.forName("com.mysql.jdbc.Driver"); //2.获取连接 conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode","root","123456"); //3.获取预编译的数据库操作对象 String sql = "select * from t_user where loginName = ? and ?";// SQL语句的框架。其中一个?表示一个占位符,一个?将来接收一个“值”,注意:占位符不能使用单括号括起来。 ps = conn.prepareStatement(sql); //给占位符?传值(第1个?下标是1,第2个问号下标是2,JDBC中所有的下标从一开始) 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; } /** *初始化用户界面 *return用户输入的用户名和密码等登录信息 */ private static Map<String,String> initUI() { Scanner s = new Scanner(System.in); System.out.println("用户名:"); String loginName = s.nextLine(); System.out.println("密码:"); String loginPwd = s.nextLine(); Map<String,String>userLoginInfo = new HashMap<>(); userLoginInfo.put("loginName",loginName); userLoginInfo.put("loginPwd",loginPwd); return userLoginInfo; } }
Statement和PreparedStatement的对比
Statement存在SQL注入问题,PreparedStatement解决了SQL注入的问题。
Statement是编译一次执行一次,PreparedStatement是编译一次可执行N次。PreparedStatement效率较高一些。
PreparedStatement会在编译阶段做类型的安全检查。
综上所述:PreparedStatement使用较多,只有极少数情况下需要使用Statement。