在之前的增删改查的操作中,使用的是Statement传输器对象,而在开发中我们用的更多的传输器对象是PreparedStatement对,PreparedStatement是Statement的子接口,比Statement更加安全,并且能够提高程序执行的效率。
例如:用户登录的操作
1.准备工作
(1)准备数据库
drop database if exists login;
create database login charset utf8;
use login;
create table user
(
id int primary key auto_increment,
username varchar(50),
password varchar(50)
);
insert into user values(null,'鲁班','123');
insert into user values(null,'妲己','234');
insert into user values(null,'王昭君','456');
insert into user values(null,'兰陵王','654');
insert into user values(null,'张三','123');
insert into user values(null,'李四','234');
用Windows+R打开cmd窗口,创建数据库有关信息:
进入数据库:
将准备好的数据库复制粘贴进去:
用以下语句查询login数据库是否插入成功:
Show Databases;
利用use语句来切换数据库:
Use login;
用以下语句查看创建的表:
show tables;
然后用describe语句查看我们创建好的user表的属性:
describe user;
最后用以下语句查看user表中插入的内容:
select * from user;
这样,数据库就已经准备好了!
(2)准备Java
打开Eclipse创建一个Java项目LoginTest,在项目里面创建一个lib文件夹用户来放.jar包,并buid path,如图:
在创建好的LoginTest项目里面创建俩个类:工具类(JdbcUtil.java)、登陆类(LoginUser.java)
具体实现代码如下:
(1)JdbcUtil.java类
package com.myself.util;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
*
* jdbc工具类
*
*/
public class JdbcUtil {
/**
* 注册驱动并获取连接对象
* @return conn连接对象
*/
public static Connection getConn() {
try {
//注册驱动并获取连接
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/login?characterEncoding=utf-8",
"root","123456"
);
//将连接器返回
return conn;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 释放jdbc程序中的资源
* @param conn 连接对象
* @param stat 传输器对象
* @param rs 结果集对象
*/
public static void close(Connection conn,Statement stat,ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}finally {
rs = null;
}
}
if (stat != null) {
try {
stat.close();
} catch (SQLException e) {
e.printStackTrace();
}finally {
stat = null;
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}finally {
conn = null;
}
}
}
}
(2)LoginUser.java类
package com.myself.login;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Scanner;
import com.myself.util.JdbcUtil;
/**
* 用户登录
*
*/
public class LoginUser {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
//1.提示用户登录
System.out.println("请登录:");
//2.提示用户输入用户名并接收用户名
System.out.println("请输入用户名:");
String user = sc.nextLine();
//3.提示用户输入密码并接收密码
System.out.println("请输入密码:");
String pwd = sc.nextLine();
//4.调用login方法实现登录
login(user,pwd);
}
/**
* 根据用户名和密码查询user表,
* 如果能够查询到表中数据,那么登录成功;
* 如果不能查询到表中数据,那么登录失败。
* @param user 用户名
* @param pwd 密码
*/
private static void login(String user, String pwd) {
//1.先声明连接并导入java.sql包
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
//2.抛异常try{}catch(){}
try {
//注册驱动并获取连接
conn = JdbcUtil.getConn();
//获取传输器并发送SQL语句到服务器执行,返回结果集
stat = conn.createStatement();
String sql = "select * from user where username='"+user+"' and password='"+pwd+"'";
rs = stat.executeQuery(sql);
//处理结果
if (rs.next()) {
//返回true时
System.out.println("登录成功!");
}else {
//返回false时
System.out.println("登录失败!");
}
} catch (Exception e) {
e.printStackTrace();
}finally {
JdbcUtil.close(conn, stat, rs);
}
}
}
写好代码之后,开始运行:回到LoginUser.java,鼠标右键Run As,选中Java Application
运行结果如下:
输入数据库里面的内容测试:登录成功!
输入不属于数据库里面的内容测试:登陆失败!
问题来了,如何用PreparedStatement代替Statement???,我们可以测试一下:打印sql以语句,如图所示:
然后右键Run As执行第一次:
请输入用户名:
鲁班'#'
请输入密码:
select * from user where username='鲁班'#'' and password=''
恭喜您登录成功!
右键执行第二次:
请输入用户名:
鲁班' or '2=2
请输入密码:
select * from user where username='鲁班' or '2=2' and password=''
恭喜您登录成功!
右键执行第三次:
请输入用户名:
请输入密码:
' or '2=2
select * from user where username='' and password='' or '2=2'
恭喜您登录成功!
通过以上的测试会发现,即使用户名或者密码不输入、用户名或密码为空,结果显示的都是登录成功,为什么呢?我们想要的结果是这样的么?
分析:
(a)select * from user where username='鲁班'#'' and password='',这个语句仔细看会发现一个问题,显示出来跟我们代码里面的SQL语句不一样。是因为在这句SQL语句里,'#'代表的是注释。'#'后面的内容都被注释掉,不会被显示出来,只要满足select * from user where username='鲁班',这个语句是对的,那么就被默认是正确的。SQL语句被篡改
(b)select * from user where username='鲁班' or '2=2' and password=' ',这个语句在SQL语句里面分成俩个部分来看,先执行and语句:'2=2' and password=' ',这个语句的结果是false,然后再执行or语句,就会发现,or前半部分是true,后半部分是false,所以结果还是true。SQL语句被篡改
(c)or语句只要满足一个条件是真的,整个语句就是真的,所以第三个也同理。SQL语句被篡改
所以,这三个语句虽然输入的不一样,但是所造成的结果是一样的,都造成了SQL语句被篡改。
这种是以前的常见的SQL注入攻击,所以要怎么防止呢?
首先,要了解SQL注入攻击产生的原因:
SQL注入攻击: 是由于后台的SQL语句是拼接而来的, 其中的参数是用户提交过来:
String sql = "select * from user where username='"+user+"' and password='"+pwd+"'";
常用的俩种解决办法:
(1)使用正则表达式对用户提交的参数进行校验。
比如:对用户输入的密码,用户名检查一下有没有一些特殊的符号(空格,#......)
(2)使用PreparedStatement对象来替代Statement对象。
所以,我们使用的是第二种方式解决问题:使用PreparedStatement来代替Statement作为传输器对象使用:
a.将LoginUser.java复制一份在com.myself.login包中,复制的类名叫做LoginUser2.java
b.需要修改一些内容,
修改后的代码示例:
package com.myself.login;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Scanner;
import com.myself.util.JdbcUtil;
import java.sql.PreparedStatement;
/**
* 用户登录
*
*/
public class LoginUser2 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
//1.提示用户登录
System.out.println("请登录:");
//2.提示用户输入用户名并接收用户名
System.out.println("请输入用户名:");
String user = sc.nextLine();
//3.提示用户输入密码并接收密码
System.out.println("请输入密码:");
String pwd = sc.nextLine();
//4.调用login方法实现登录
login(user,pwd);
}
/**
* 根据用户名和密码查询user表,
* 如果能够查询到表中数据,那么登录成功;
* 如果不能查询到表中数据,那么登录失败。
* @param user 用户名
* @param pwd 密码
*/
private static void login(String user, String pwd) {
//1.先声明连接并导入java.sql包
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
//2.抛异常try{}catch(){}
try {
//注册驱动并获取连接
conn = JdbcUtil.getConn();
//获取传输器并发送SQL语句到服务器执行,返回结果集
String sql = "select * from user where username=? and password=?";
ps = conn.prepareStatement(sql);
//设置SQL参数
/**
*有几个问号就设置几个参数,没有问号就不需要设置参数
*/
ps.setString(1, user);
ps.setString(2, pwd);
//执行SQL语句
ps.executeQuery();
//处理结果
if (rs.next()) {
//返回true时
System.out.println("登录成功!");
}else {
//返回false时
System.out.println("登录失败!");
}
} catch (Exception e) {
e.printStackTrace();
}finally {
JdbcUtil.close(conn, ps, rs);
}
}
}
再次执行程序,按照上面的操作登录。
已经成功的防止了SQL注入攻击问题了。
使用PreparedStatement对象可以防止SQL注入攻击,而且通过方法设置参数更加的方便且不易出错!还可以从某些方面提高程序执行的效率!