Mysql版本:8.0.26
可视化客户端:sql yog
编译软件:IntelliJ IDEA 2019.2.4 x64
运行环境:win10 家庭中文版
jdk版本:1.8.0_361
前言
随着人工智能与大数据的不断发展,与数据库打交道越来越频繁,很多开发者都开启了学习数据库的脚步,相应地,特别是对Java开发者而言,使用jdbc与数据库的交互难免遇到各种问题,如避免sql注入攻击等问题。本篇着重介绍该部分。
提示:以下是本篇文章正文内容,下面案例可供参考
一、避免sql拼接问题
1.1 准备测试数据
👉创建数据表t_employee
CREATE TABLE `t_employee` ( `eid` int NOT NULL AUTO_INCREMENT COMMENT '员工编号', `ename` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '员工姓名', `salary` double NOT NULL COMMENT '薪资', `commission_pct` decimal(3,2) DEFAULT NULL COMMENT '奖金比例', `birthday` date NOT NULL COMMENT '出生日期', `gender` enum('男','女') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '男' COMMENT '性别', `tel` char(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '手机号码', `email` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '邮箱', `address` varchar(150) DEFAULT NULL COMMENT '地址', `work_place` set('北京','深圳','上海','武汉') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '北京' COMMENT '工作地点', `hiredate` date NOT NULL COMMENT '入职日期', `job_id` int DEFAULT NULL COMMENT '职位编号', `mid` int DEFAULT NULL COMMENT '领导编号', `did` int DEFAULT NULL COMMENT '部门编号', PRIMARY KEY (`eid`), KEY `job_id` (`job_id`), KEY `did` (`did`), KEY `mid` (`mid`), CONSTRAINT `t_employee_ibfk_1` FOREIGN KEY (`job_id`) REFERENCES `t_job` (`jid`) ON DELETE SET NULL ON UPDATE CASCADE, CONSTRAINT `t_employee_ibfk_2` FOREIGN KEY (`did`) REFERENCES `t_department` (`did`) ON DELETE SET NULL ON UPDATE CASCADE, CONSTRAINT `t_employee_ibfk_3` FOREIGN KEY (`mid`) REFERENCES `t_employee` (`eid`) ON DELETE SET NULL ON UPDATE CASCADE, CONSTRAINT `t_employee_chk_1` CHECK ((`salary` > 0)), CONSTRAINT `t_employee_chk_2` CHECK ((`hiredate` > `birthday`)) ) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1.2 问题演示
👉问题描述:
JDBC的SQL拼接问题指的是在使用JDBC进行数据库操作时,如果直接将用户输入的参数拼接到SQL语句中,可能会存在SQL注入的安全风险。
SQL注入是一种常见的网络攻击方式,攻击者通过在输入参数中注入恶意的SQL代码,从而可以执行非授权的数据库操作,例如删除或修改数据、窃取敏感信息等。
👉应对措施:
在编写JDBC程序时应该避免直接将用户输入的参数拼接到SQL语句中,而应该使用预编译语句或参数化查询的方式来构建SQL语句。
👉何为参数化查询与预编译语句?
二者实现的基本思路都是一样的:
都是将SQL语句和参数分开处理
代码演示如下:
//我们定义了一个以指定名字为条件查询特定用户信息的sql语句 //使用一个占位符"?"来代替where中name的值(这里用到了参数化查询技术) String sql = "SELECT * FROM users WHERE name = ?"; //创建一个PreparedStatement对象包裹sql语句 /* PreparedStatement与一般的Statment流不一样,它会将sql语句提前编译好,并将编译结果缓存起来,避免每次执行sql都需要编译的时间花销。(这里用到了预编译技术) */ PreparedStatement stmt = conn.prepareStatement(sql); //用setString方法将参数值设置为"Jack" //并将它传递给PreparedStatement对象的第一个参数位置,即"name= ?"的"?"位置 stmt.setString(1, "jack"); //执行executeQuery(),真正地发送sql语句过去查询并返回查询的结果集 ResultSet rs = stmt.executeQuery();
案例:尝试给t_employee表添加一条完整的记录,记录内容自定义
👉未使用参数化查询
代码演示如下:
@Test public void test01() throws SQLException { Scanner input=new Scanner(System.in); System.out.print("请输入姓名:"); String ename=input.next(); System.out.print("请输入薪资:"); String salary=input.next(); System.out.print("请输入出生日期:"); String birthday=input.next(); System.out.print("请输入性别:"); String gender=input.next(); System.out.print("请输入电话:"); String tel=input.next(); System.out.print("请输入邮箱:"); String email=input.next(); String url="jdbc:mysql://localhost:3306/atguigu?serverTimezone=UTC"; Connection root = DriverManager.getConnection(url, "root", "123456"); // System.out.println(root); //把上面的数据插入到数据库中atguigu中的t_employee表中 //这样一个个添加变量,太麻烦了 String sql="insert into t_employee(ename,salary,birthday,gender,tel,email,hiredate) values('"+ename+"',"+salary+",'"+birthday+"','"+gender+"','"+tel+"','"+email+"',curdate())"; PreparedStatement pst = root.prepareStatement(sql); int len = pst.executeUpdate(); System.out.println(len>0?"添加成功":"添加失败"); input.close(); pst.close(); root.close(); }
👉使用参数化查询
代码演示如下:
@Test public void test02() throws SQLException { Scanner input=new Scanner(System.in); System.out.print("请输入姓名:"); String ename=input.next(); System.out.print("请输入薪资:"); String salary=input.next(); System.out.print("请输入出生日期:"); String birthday=input.next(); System.out.print("请输入性别:"); String gender=input.next(); System.out.print("请输入电话:"); String tel=input.next(); System.out.print("请输入邮箱:"); String email=input.next(); String url="jdbc:mysql://localhost:3306/atguigu?serverTimezone=UTC"; Connection root = DriverManager.getConnection(url, "root", "123456"); //多加一步,把?用具体的变量、表达式等值代替 String sql="insert into t_employee(ename,salary,birthday,gender,tel,email,hiredate) values(?,?,?,?,?,?,?)"; PreparedStatement pst = root.prepareStatement(sql); pst.setObject(1,ename);//这里的1代替第一个?,用ename变量的值代替第一个?的位置 pst.setObject(2,salary);//这里的2代替第一个?,用salary变量的值代替第二个?的位置,如下类推 pst.setObject(3,birthday); pst.setObject(4,gender); pst.setObject(5,tel); pst.setObject(6,email); pst.setObject(7,new Date()); int len = pst.executeUpdate(); System.out.println(len>0?"添加成功":"添加失败"); input.close(); pst.close(); root.close(); }
二、避免sql注入问题
2.1 准备测试数据
👉创建数据表t_employee
CREATE TABLE `t_employee` ( `eid` int NOT NULL AUTO_INCREMENT COMMENT '员工编号', `ename` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '员工姓名', `salary` double NOT NULL COMMENT '薪资', `commission_pct` decimal(3,2) DEFAULT NULL COMMENT '奖金比例', `birthday` date NOT NULL COMMENT '出生日期', `gender` enum('男','女') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '男' COMMENT '性别', `tel` char(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '手机号码', `email` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '邮箱', `address` varchar(150) DEFAULT NULL COMMENT '地址', `work_place` set('北京','深圳','上海','武汉') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '北京' COMMENT '工作地点', `hiredate` date NOT NULL COMMENT '入职日期', `job_id` int DEFAULT NULL COMMENT '职位编号', `mid` int DEFAULT NULL COMMENT '领导编号', `did` int DEFAULT NULL COMMENT '部门编号', PRIMARY KEY (`eid`), KEY `job_id` (`job_id`), KEY `did` (`did`), KEY `mid` (`mid`), CONSTRAINT `t_employee_ibfk_1` FOREIGN KEY (`job_id`) REFERENCES `t_job` (`jid`) ON DELETE SET NULL ON UPDATE CASCADE, CONSTRAINT `t_employee_ibfk_2` FOREIGN KEY (`did`) REFERENCES `t_department` (`did`) ON DELETE SET NULL ON UPDATE CASCADE, CONSTRAINT `t_employee_ibfk_3` FOREIGN KEY (`mid`) REFERENCES `t_employee` (`eid`) ON DELETE SET NULL ON UPDATE CASCADE, CONSTRAINT `t_employee_chk_1` CHECK ((`salary` > 0)), CONSTRAINT `t_employee_chk_2` CHECK ((`hiredate` > `birthday`)) ) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
2.2 问题演示
👉问题描述:
SQL注入是一种攻击技术,攻击者通过在应用程序中输入恶意的SQL语句,从而访问或篡改数据库中的数据。这种攻击技术可以导致数据泄露、数据损坏或者整个系统崩溃。
代码演示如下:
@Test public void test01() throws SQLException { Scanner input=new Scanner(System.in); System.out.print("请输入员工编号:"); String eid=input.next(); //输入 eid=1 or 1=1 表中的数据全都出现了,有问题, //连接数据看库 String url="jdbc:mysql://localhost:3306/atguigu?serverTimezone=UTC"; Connection root = DriverManager.getConnection(url, "root", "123456"); //存在sql拼接问题 String sql="select * from t_employee where eid ="+eid; //使用了预编译技术 PreparedStatement pst = root.prepareStatement(sql);//把sql语句装进PreparedStatement对象中,准备发送 ResultSet resultSet = pst.executeQuery();//返回一个结果集;真正发送sql //结果集中有一个 元数据对象 //元数据:描述数据的数据,对数据进行描述的信息,例如:数据列有多少列,数据的列名称等 //获取atguigu下的t_employeet表的元数据 ResultSetMetaData metaData = pst.getMetaData(); int columnCount = metaData.getColumnCount();//获取结果集中有几列 while (resultSet.next()){//循环查看表的每一行记录 for (int i = 1; i <= columnCount ; i++) {//for循环遍历每一行记录的每一个列,即单元格 System.out.print(resultSet.getObject(i)+"\t"); } System.out.println(); } input.close(); pst.close(); root.close(); }
👉备注:
此处查询到的结果集中有一个 元数据对象metaData
元数据:描述数据的数据,对数据进行描述的信息。
例如:数据列有多少列,即
数据的列名称
等
//获取atguigu下的t_employeet表的元数据 ResultSetMetaData metaData = pst.getMetaData(); int columnCount = metaData.getColumnCount();//获取结果集中有几列 //for循环遍历元数据的每一个列,即单元格 for (int i = 1; i <= columnCount ; i++) { //获取一行记录中每个单元格的内容 System.out.print(resultSet.getObject(i)+"\t"); }
👉原因分析:
当我输入 1 or 1=1 时,完全的sql语句如下:
#这是一个经典的sql注入攻击语句 select * from t_employee where eid =1 or 1=1 ; #查询t_employee表中eid为1或1=1的员工信息
其中的“or 1
"=1”条件会使查询返回所有记录,无论eid是否等于1。攻击者可以通过这种方式绕过身份验证和权限控制,获取到数据库中t_employeeb表的所有数据。
👉解决措施:
使用参数化查询
@Test public void test02() throws SQLException { Scanner input=new Scanner(System.in); System.out.print("请输入员工编号:"); String eid=input.next(); //输入 eid=1 or 1=1 表中的数据全都出现了,有问题, //连接数据看库 String url="jdbc:mysql://localhost:3306/atguigu?serverTimezone=UTC"; Connection root = DriverManager.getConnection(url, "root", "123456"); //使用”?“避免sql注入问题 String sql="select * from t_employee where eid = ?"; //select * from t_employee where eid =1 or 1=1 ; 1=1 永远成立 PreparedStatement pst = root.prepareStatement(sql);//把sql语句装进PreparedStatement对象中,准备发送 pst.setObject(1,eid);//这里1代表sql语句中的?,用eid的值代替?的位置 ResultSet resultSet = pst.executeQuery();//返回一个结果集;真正发送sql //结果集中有一个 元数据对象 //元数据:描述数据的数据,对数据进行描述的信息,例如:数据列有多少列,数据的列名称等 //获取atguigu下的t_employeet表的元数据(即数据表中各个字段名) ResultSetMetaData metaData = pst.getMetaData(); int columnCount = metaData.getColumnCount();//获取结果集中有几列 while (resultSet.next()){//循环查看表的每一行记录 for (int i = 1; i <= columnCount ; i++) {//for循环遍历每一行记录的每一个列,即单元格 System.out.print(resultSet.getObject(i)+"\t"); } System.out.println(); } input.close(); pst.close(); root.close(); }
三、java程序读取图片等给MySQL数据库字段中的blob等二进制类型数据赋值
3.1 准备测试数据
👉 建立数据表t_user
CREATE TABLE `t_user` ( `username` VARCHAR(20) NOT NULL, `password` VARCHAR(50) NOT NULL, `photo` BLOB );