JDBC中的SQL注入

1.JDBC中sql注入

1.1 JDBC

JDBC的概念:JDBC是sun公司(已被Oracle收购)制定一系列接口标准,由不同数据库厂商(Oracle、MySQL等)实现接口方法并封装成驱动文件,供开发人员操作数据库。也就是说,开发人员可采用统一的代码来操作不同的数据库,而无需关注驱动文件的内部实现。
通俗的理解就是JDBC是规范、是接口,不同的数据库厂商要据此编写各自的操作实现。

1.2 SQL注入

我们知道,dao层(负责操作数据库,数据访问层)中执行的SQL语句是拼接出来的,其中有一部分数据是由用户从客户端传入,所以当用户传入的数据中包含SQL关键字时,就有可能通过这些关键字改变SQL语句的语义,从而执行一些特殊的操作,这样的攻击方式就叫做SQL注入攻击。
sql注入案例演示:

	private static boolean Login(Map<String, String> userLoginInfo) {
		// TODO Auto-generated method stub
		boolean loginSuccess= false;
		String nameString= userLoginInfo.get("usernameString");
		String pwdString = userLoginInfo.get("passwordString");
		Connection con = null;
		Statement statement  = null;
		ResultSet resultSet = null;
		try {
			//1.注册驱动
			Class.forName("com.mysql.jdbc.Driver");
			//2.获取连接
			con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test","root","");
			//3.获取数据库操作对象
			statement = con.createStatement();
			//4.执行sql语句
			String sql = "select * from user where username='"+nameString+"' and password='"+pwdString+"'";
			//"select * from user where username='"+nameString+"' and password='fdas' or '1' = '1'"
			System.out.println("用户名"+nameString);
			System.out.println("密码"+pwdString);
			resultSet = statement.executeQuery(sql);
			//5.处理结果集
			if (resultSet.next()) {
				//登录成功
				loginSuccess = true;
			}
		}catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}finally {
			if (resultSet !=null) {
				try {
					resultSet.close();
				} catch (Exception e) {
					// TODO: handle exception
					e.printStackTrace();
				}
			}
			if (statement !=null) {
				try {
					statement.close();
				} catch (Exception e) {
					// TODO: handle exception
					e.printStackTrace();
				}
			}
			if (con !=null) {
				try {
					con.close();
				} catch (Exception e) {
					// TODO: handle exception
					e.printStackTrace();
				}
			}
		}
		return loginSuccess;
	}

程序的逻辑为:

用户输入用户名和密码

在数据库中查找匹配的用户名和密码,若有匹配的则登录成功,否则,提示"登陆失败"。实际就是在数据库中执行以下sql语句

String sql = “select * from user where username='”+nameString+“’ and password='”+pwdString+“'”;

上边代码的逻辑看似没有问题,实则问题很大:

当用户这样输入:一个数据库中不存在的用户名,例如bbb,然后输入密码:a’ or ‘a’ = 'a,这样输入竟然也能登陆成功。
如图:
在这里插入图片描述

原因:
当用户输入以上用户名和密码时,程序就会在数据库中执行以下sql语句
sql :select * from user where username = ‘bbb’ and password = ‘a’ or ‘a’ = ‘a’ ;

这是一条恒等式,数据库会返回user表中所有的内容,这样rs.next()就会等于true,所以可以登陆成功。

1.3 SQL注入的预防

使用PreparedStatement代替Statement可以有效防止SQL注入的发生。

PreparedStatement利用预编译的机制将sql语句的主干和参数分别传输给数据库服务器,从而使数据库分辨的出哪些是sql语句的主干哪些是参数,这样一来即使参数中带了sql的关键字,数据库服务器也仅仅将他当作参数值使用,关键字不会起作用,从而从原理上防止了sql注入的问题。
上面的sql语句改写为:

String sql = "select * from user where username= ? and password= ?";

再去给每个 ?占位符赋值

sta.setString(1, nameString);
sta.setString(2, pwdString);

使用PrepareStatement预防sql注入演示:

private static boolean Login(Map<String, String> userLoginInfo) {
		// TODO Auto-generated method stub
		boolean loginSuccess= false;
		String nameString= userLoginInfo.get("usernameString");
		String pwdString = userLoginInfo.get("passwordString");
		Connection con = null;
		PreparedStatement sta  = null;
		ResultSet resultSet = null;
		try {
			//1.注册驱动
			Class.forName("com.mysql.jdbc.Driver");
			//2.获取连接
			con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test","root","");
			//3.获取数据库操作对象
			//SQL语句的框子 其中一个?,表示一个占位符 ,一个?将来接收一个"值",注意占位符不能使用单引号括起来
			String sql = "select * from user where username= ? and password= ?";//sql语句的框子
			//程序执行到此处,会发送sql语句框子给DBMS,然后DBMS对sql语句进行预编译
			sta = con.prepareStatement(sql);
			//给占位符?传值(第一个问号的下标是1,第二个问号的下标是2,JDBC中所有下标都是从1开始的)
			sta.setString(1, nameString);
			sta.setString(2, pwdString);
			//4.执行sql
			System.out.println("用户名:"+nameString);
			System.out.println("密码:"+pwdString);
			resultSet = sta.executeQuery();
			//5.处理结果集
			if (resultSet.next()) {
				//登录成功
				loginSuccess = true;
			}
		}catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}finally {
			if (resultSet !=null) {
				try {
					resultSet.close();
				} catch (Exception e) {
					// TODO: handle exception
					e.printStackTrace();
				}
			}
			if (sta !=null) {
				try {
					sta.close();
				} catch (Exception e) {
					// TODO: handle exception
					e.printStackTrace();
				}
			}
			if (con !=null) {
				try {
					con.close();
				} catch (Exception e) {
					// TODO: handle exception
					e.printStackTrace();
				}
			}
		}
		return loginSuccess;
	}

测试:
在这里插入图片描述

2. 总结

PreparedStatement主要有如下的三个优点:

  1. 可以防止sql注入
  2. 由于使用了预编译机制,执行的效率要高于Statement
  3. sql语句使用?形式替代参数,然后再用方法设置?的值,比起拼接字符串,代码更加优雅.

PreparedStatement 与Statment比较:

  1. 语法不同:PreparedStatement可以使用预编译的sql,而Statment只能使用静态的sql
  2. 效率不同: PreparedStatement可以使用sql缓存区,效率比Statment高
  3. 安全性不同: PreparedStatement可以有效防止sql注入,而Statment不能防止sql注入。

搭建数据库表的代码:
create table t_user(
`id` int(10) AUTO_INCREMENT PRIMARY KEY,
`username` VARCHAR(25) ,
`password` VARCHAR(25)
) ENGINE=INNODB;
--插入两条数据方便测试
insert into t_user(username,password) values('curry','123456')
insert into t_user(username,password) values('rose','123456')
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值