Jdbc学习笔记(四)--PreparedStatement对象、sql攻击(安全问题)
目录
(一)使用PreparedStatement对象的原因:
使用Statement对象编写sql语句会遇到的问题
使用Statement对象编写sql语句,都是拼接sql
问题:
- 可读性差
- 编写复杂,容易出错
- 安全问题,sql攻击(sql注入) 最大问题,系统会被攻破
(二)sql攻击
1.什么是sql攻击
在需要用户输入的地方,用户输入的是SQL语句的片段,最终用户输入的SQL片段与我们DAO中写的SQL语句合成一个完整的SQL语句!例如用户在登录时输入的用户名和密码都是为SQL语句的片段!
2.演示sql攻击
首先我们需要创建一张用户表,并插入数据,用来存储用户的信息
下面我们写一个login()方法!
public static boolean login(String username, String password) {
Connection conn=null;
Statement stmt=null;
ResultSet rs=null;
try {
Class.forName("com.mysql.jdbc.Driver");
conn= DriverManager.getConnection("jdbc:mysql://localhost:3306/esa?useSSL=false","root","123456");
String sql="select count(1) num from user where username='"+username+"' and password='"+password+"'";
System.out.println(sql);
stmt=conn.createStatement();
rs=stmt.executeQuery(sql);
rs.next();
int count=rs.getInt(1);
return count >= 1;
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}catch (SQLException e) {
throw new RuntimeException(e);
} finally {
try{
if (rs!=null) rs.close();
if (stmt!=null) stmt.close();
if (conn!=null) conn.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}细节:
throw new RuntimeException(e); 不能写成e.printStackTrace(),不然会报错,无法运行
e.printStackTrace( )是打印异常栈信息
throw new RuntimeException(e)是把异常包在一个运行时异常中抛出。
因为return count >= 1,系统会无法识别return的是一个什么值,会显示缺少return语句;
下面是调用这个方法的代码:
login("a' or '1'='1", "b' or '1'='1");
控制台输出登陆成功,因为是输入的用户名和密码是SQL语句片段,最终与我们的login()方法中的SQL语句组合在一起!我们来看看组合在一起的SQL语句:
select count(1) from tb_user where username = 'a' or '1'='1' and password='b' or '1'='1'
因为 'a' or '1 = 1'这个语句,or作为关键字只要有一边返回的是true那么这个整体的值就是true,右边始终返回true,所以不管左边是否正确,这个值永远是true,那么就很容易被别人登录进去,从而造成安全问题。
(三)防止SQL攻击
jdk中提供一个Statement的子接口,java.sql.PreparedStatement
Statement在创建时不需要传sql语句,而是把sql语句拼接起来,发送给mysql服务器
PreparedStatement是预编译的Statement,可以使用占位符?,给值占位
1.PreparedStatement是什么
PreparedStatement叫预编译声明!
什么是预编译:在sql语句执行之前,要先进行两个步骤,sql编译和sql语法分析,在语句第一遍执行完毕以后,PreparedStatement会将预编译结果保存下来,在一下遇到相同的sql语句时,就不需要再重新进行一遍这三个步骤,可以跳过前两个,这也是为什么效率提高了的原因
PreparedStatement是Statement的子接口,你可以使用PreparedStatement来替换Statement。
PreparedStatement的好处:
防止SQL攻击;
提高代码的可读性,以可维护性;
提高效率。
2. PreparedStatement的使用
使用Connection的prepareStatement(String sql):即创建它时就让它与一条SQL模板绑定;
调用PreparedStatement的setXXX()系列方法为问号设置值
调用executeUpdate()或executeQuery()方法,但要注意,调用没有参数的方法;
接下来我们来进行演示:
对之前的代码进行修改:
1.将拼接的username和password换成?
2.创建PreparedStatement对象,将Statement改成PreparedStatement
3.因为PreparedStatement的executeQuery()和executeUpdate()方法是无参的,所以要将括号中的sql删除
4.给?赋值
PreparedStatement提供了一组setXxx(int 问号的索引,值)方法,问号索引从1开始
如果不知道列的数据类型,有一个通用数据类型:setObject()
代码:
public static boolean login2(String username, String password) {
Connection conn=null;
PreparedStatement stmt=null;
ResultSet rs=null;
try {
Class.forName("com.mysql.jdbc.Driver");
conn= DriverManager.getConnection("jdbc:mysql://localhost:3306/esa?useSSL=false","root","123456");
String sql="select count(1) num from tb_user where username = ? and password= ?";
System.out.println(sql);
stmt = conn.prepareStatement(sql);
//----------------
//给?赋值
//----------------
stmt.setString(1,username);
stmt.setString(2,password);
rs=stmt.executeQuery();
rs.next();
int count=rs.getInt(1);
return count >= 1;
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}catch (SQLException e) {
throw new RuntimeException(e);
} finally {
try{
if (rs != null) rs.close();
if (stmt != null) stmt.close();
if (conn != null) conn.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
执行结果:
登录失败!!,防止sql攻击成功
3.总结:
所以,建议大家在今后的开发中,无论什么情况,都去需要PreparedStatement,而不是使用Statement。
原文地址:https://blog.csdn.net/2301_76890677/article/details/143771345
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!