java.sql.SQLSyntaxErrorException: Unknown column ‘xxx‘ in ‘where clause‘问题解决及防止SQL注入

本文讲述了SQL注入问题的成因,如何通过PreparedStatement解决,以及Statement和PreparedStatement的性能对比。重点强调了使用PreparedStatement预防注入并提升性能。
摘要由CSDN通过智能技术生成

1、错误解决

犯了低级错误,由于拼凑sql语句时对字符类型数据没有用引号引起来造成:java.sql.SQLSyntaxErrorException: Unknown column 'xxx' in 'where clause'
原始:

String sql = "select * from user where username= "+ name + " and password=" + pwd ;

当传入的name和pwd为int等整型则不会出任何错误,如果是字符或字符串则需要用引号引起来
就比如正常的不是传入参数:select * from user where name='11';就要用引号所以这里也必须拼接引号,将上面sql语句修改为:

String sql = "select * from user where username= '"+ name + "' and password= '" + pwd  +"'";

问题解决!

2、SQL注入

另外,这里还存在SQL注入的问题:
比如传入的name为sunny,pwd为1111’ or ‘1’=‘1
因为’1’='1’恒成立,所以相当于where后面的条件无效该sql语句可以查询出所有表中的数据,这种现象被称为SQL注入。
产生SQL注入的根本原因:
用户输入的信息中含有sql语句的关键字,并且这些关键字参与了sql语句的编译过程,导致sql语句的愿意被扭曲,进而形成了SQL注入。

2.1、解决SQL注入问题

方法1:只要用户提供的信息不参与SQL语句的编译过程,问题就解决了,要想如此则必须将数据库操作对象Statement换成PreparedStatement(叫预编译的数据库操作对象)。

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

举例:

package com.sunny;
import java.sql.*;
import java.util.*;

/**
 * 模拟用户登录
 */
public class JDBCTest06 {
    public static void main(String[] args) {

        Map<String, String> userInfo;
        //进行初始化
        userInfo = init();
        String username = userInfo.get("用户名");
        String pwd = userInfo.get("密码");
        //登录验证
        boolean result = login(username, pwd);
        System.out.println(result ? "登录成功!" : "登录失败!");
    }

    /**
     * 初始化
     *
     * @return Map<String , String>返回用户输入的信息
     */
    private static Map<String, String> init() {
        Map<String, String> userInfo = new HashMap<>();
        System.out.println("请输入用户名:");
        Scanner sc = new Scanner(System.in);
        String name = sc.nextLine();
        System.out.println("请输入密码:");
        String pwd = sc.nextLine();
        userInfo.put("用户名", name);
        userInfo.put("密码", pwd);
        return userInfo;
    }

    /**
     * 登录
     * @param name 用户名
     * @param pwd  密码
     * @return 返回是否在数据库中找到匹配的记录
     */
    private static boolean login(String name, String pwd) {
        //进行数据库连接验证
        //创建资源绑定器
        //db.properties文件必须在src路径下
//        ResourceBundle resourceBundle = ResourceBundle.getBundle("db");//直接位于src目录下
        ResourceBundle resourceBundle = ResourceBundle.getBundle("com/sunny/db");//在src的子目录com/sunny下
        String driver = resourceBundle.getString("driver");
        String url = resourceBundle.getString("url");
        String username = resourceBundle.getString("username");
        String password = resourceBundle.getString("password");
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        boolean flag = false;
        try {
            //注册驱动
            Class.forName(driver);
            //获取连接
            conn = DriverManager.getConnection(url, username, password);
            //获取数据库操作对象
            stmt = conn.createStatement();
            //执行sql语句
            String sql = "select * from user where username= '"+ name + "' and password= '" + pwd  +"'";
            rs = stmt.executeQuery(sql);
            
            //处理查询结果集
            if (rs.next()) {
                flag = true;
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //释放资源
            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  flag;
    }
}

db.properties:

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/mytest?serverTimezone=Asia/Shanghai
username=root
password=123456

此时user表中数据为:
在这里插入图片描述
此时编写造成SQL注入的输入信息【name:sunny,pwd:1111’ or ‘1’=‘1’】,运行结果:
在这里插入图片描述
如上,是登录成功,说明产生了SQL注入。

改进:用PreparedStatement替代Statement,防止SQL注入
修改后代码:

package com.sunny;
import java.sql.*;
import java.util.*;

/**
 * 模拟用户登录(用PreparedStatement替代Statement,防止SQL注入)
 */
public class JDBCTest07 {
    public static void main(String[] args) {
        Map<String, String> userInfo;
        //进行初始化
        userInfo = init();
        String username = userInfo.get("用户名");
        String pwd = userInfo.get("密码");
        //登录验证
        boolean result = login(username, pwd);
        System.out.println(result ? "登录成功!" : "登录失败!");
    }

    /**
     * 初始化
     *
     * @return Map<String , String>返回用户输入的信息
     */
    private static Map<String, String> init() {
        Map<String, String> userInfo = new HashMap<>();
        System.out.println("请输入用户名:");
        Scanner sc = new Scanner(System.in);
        String name = sc.nextLine();
        System.out.println("请输入密码:");
        String pwd = sc.nextLine();
        userInfo.put("用户名", name);
        userInfo.put("密码", pwd);
        return userInfo;
    }

    /**
     * 登录
     * @param name 用户名
     * @param pwd  密码
     * @return 返回是否在数据库中找到匹配的记录
     */
    private static boolean login(String name, String pwd) {
        //进行数据库连接验证
        //创建资源绑定器
        //db.properties文件必须在src路径下
//        ResourceBundle resourceBundle = ResourceBundle.getBundle("db");//直接位于src目录下
        ResourceBundle resourceBundle = ResourceBundle.getBundle("com/sunny/db");//在src的子目录com/sunny下
        String driver = resourceBundle.getString("driver");
        String url = resourceBundle.getString("url");
        String username = resourceBundle.getString("username");
        String password = resourceBundle.getString("password");
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        boolean flag = false;
        try {
            //注册驱动
            Class.forName(driver);
            //获取连接
            conn = DriverManager.getConnection(url, username, password);
            //获取数据库操作对象
            //使用? 表示占位符,一个?接收一个值
            String sql = "select * from user where username= ? and password= ?";
            ps = conn.prepareStatement(sql); //使用prepareStatement要传入sql
            //给占位符?传值(第一个问号的下标为1)
            ps.setString(1,name);
            ps.setString(2,pwd);
            //执行sql语句
            rs = ps.executeQuery();//不用再传入sql

            //处理查询结果集
            if (rs.next()) {
                flag = true;
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //释放资源
            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  flag;
    }
}

运行结果:
在这里插入图片描述
成功防止SQL注入!

3、对比Statement和PreparedStatement

  1. 如上所述,Statement存在SQL注入问题,PreparedStatement解决了SQL注入问题。
  2. 当批量处理SQL或频繁执行相同的查询时,Statement是编译一次,执行一次;而PreparedStatement有明显的性能上的优势,数据库可以将编译优化后的SQL语句缓存起来,下次执行相同结构的语句时就会很快(不用再次编译)。
  3. PreparedStatement在对占位符?进行传值时可以对类型进行安全性检查【限制所传递的类型,保证其传递类型准确】;

//如下,只能传入String类型的数据
ps.setString(1,name);
ps.setString(2,pwd);

综上所述,PreparedStatement使用较多,但是也有一些情况必须使用Statement
–>在有些业务中有要求必须支持SQL注入【需要拼接字符串】,则必须使用Statement。
这个内容见:演示业务中必须使用Statement完成字符串的拼接

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ED_Sunny小王

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值