signed

QiShunwang

“诚信为本、客户至上”

如何避免SQL注入

2021/3/21 8:22:34   来源:

什么是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的对比

  1. Statement存在SQL注入问题,PreparedStatement解决了SQL注入的问题。
  2. Statement是编译一次执行一次,PreparedStatement是编译一次可执行N次。PreparedStatement效率较高一些。
  3. PreparedStatement会在编译阶段做类型的安全检查。

综上所述:PreparedStatement使用较多,只有极少数情况下需要使用Statement。