1.用户登陆系统演示—Statement—SQL注入
1.1.需求:
-
模拟用户登陆功能的实现
- 用户名:zhangsan
- 密码:123
1.2.业务描述:
-
程序运行的时候,提供一个输入的入口,可以让用户输入用户名、密码 输入后,提交信息,java程序搜集到用户信息
-
JAVA程序连接数据库验证是否合法 合法:显示登陆成功 非法:显示登陆失败
1.3.本程序存在的问题—— 存在SQL语句注入现象
- 用户名:a
- 密码:b’ or ‘1’='1
- 会登录成功
1.4.导致SQL语句注入的根本原因:
- 用户输入的东西,含有sql关键字,而且编译进去了,导致系统原来的意思被扭曲!!!!
1.5.解决方法:
- 只要用户输入的SQL关键字不编译进去就可以了
1.6.执行结果 - 正确实例:
- 错误结果:
public class JDBCTestSqlInject {
public static void main(String[] args) {
//1.输入信息
Map<String, String> userLoginInfo = inintUI();
//2.验证信息是否正确
boolean loginSuccess = login(userLoginInfo);
System.out.println(loginSuccess ? "登陆成功" : "登陆失败");
}
/**
* 验证用户名,密码是否正确
* @param userLoginInfo
* @return
*/
private static boolean login (Map < String, String > userLoginInfo){
boolean flag=false;
String loginName = userLoginInfo.get("loginName");
String loginPwd = userLoginInfo.get("loginPwd");
//JDBC代码
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
//1.注册驱动(作用:告诉java程序,即将要连接哪个品牌的数据库)
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接(表示JVM的进程和数据库进程之间的 通道 打开了,使用完之后 必须关闭)
String url = "jdbc:mysql://localhost:3306/bjpowernode"; //自己的地址链接
String user = "root";
String password = "333";
conn = DriverManager.getConnection(url,user,password);
//3.获取数据库操作对象(专门执行sql语句的对象)
stmt = conn.createStatement();
//4.执行sql语句(主要执行DQL、DML……)
String sql = " select * from t_user where loginName ='"+ loginName +"'and loginPwd ='"+ loginPwd +"'";
System.out.println(sql);
//5.处理查询结果集(只有当第4步执行的是select语句时,才有这第5步)
rs = stmt.executeQuery(sql); //专门执行DQL查询语句
if(rs.next()){
flag = true;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//6.释放资源(使用完资源后一定要关闭资源,java和数据库属于进程间通信,开启后一定要关闭)
try{
if(stmt != null)
stmt.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try{
if(conn != null)
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try{
if(rs != null)
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
return flag;
}
/**
* 初始化用户界面
* @return 用户输入用户名、密码等信息
*/
private static Map<String, String> inintUI () {
Scanner s = new Scanner(System.in);
System.out.print("用户名:");
String loginName = s.nextLine();
System.out.print("密码:");
String loginPwd = s.nextLine();
Map<String, String> userLoginInfo = new HashMap<>();
userLoginInfo.put("loginName",loginName);
userLoginInfo.put("loginPwd",loginPwd);
return userLoginInfo;
}
}
2.SQL注入的解决——PreparedStatement
2.1.改动1——Statement改为PreparedStatement
-
// Statement stmt = null; PreparedStatement stmt = null;
2.2.改动:2——改为占位符—?
-
//String sql = " select * from t_user where loginName ='"+ loginName +"'and loginPwd ='"+ loginPwd +"'"; //一个问号表示一个占位符 String sql = " select * from t_user where loginName = ? and loginPwd = ? ";
2.3.改动:3——prepareStatement进行预编译
-
// stmt = conn.createStatement(); //程序执行到此,会发送sql语句”框子“给DBMS,然后它进行‘预编译 stmt = conn.prepareStatement(sql);
2.4.改动:4—— 给占位符传值
-
stmt.setString(1,loginName); stmt.setString(2,loginPwd);
public class JDBCTestSqlInjectSolve {
public static void main(String[] args) {
//1.输入信息
Map<String, String> userLoginInfo = inintUI();
//2.验证信息是否正确
boolean loginSuccess = login(userLoginInfo);
System.out.println(loginSuccess ? "登陆成功" : "登陆失败");
}
/**
* 验证用户名,密码是否正确
* @param userLoginInfo
* @return
*/
private static boolean login (Map < String, String > userLoginInfo){
boolean flag=false;
String loginName = userLoginInfo.get("loginName");
String loginPwd = userLoginInfo.get("loginPwd");
//JDBC代码
Connection conn = null;
//改动1:
// Statement stmt = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
//1.注册驱动(作用:告诉java程序,即将要连接哪个品牌的数据库)
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接(表示JVM的进程和数据库进程之间的 通道 打开了,使用完之后 必须关闭)
String url = "jdbc:mysql://localhost:3306/bjpowernode"; //自己的地址链接
String user = "root";
String password = "333";
conn = DriverManager.getConnection(url,user,password);
//3.获取数据库操作对象(专门执行sql语句的对象)
//改动:2
// String sql = " select * from t_user where loginName ='"+ loginName +"'and loginPwd ='"+ loginPwd +"'";
//一个问号表示一个占位符
String sql = " select * from t_user where loginName = ? and loginPwd = ? ";
//改动:3
// stmt = conn.createStatement();
//程序执行到此,会发送sql语句”框子“给DBMS,然后它进行‘预编译
stmt = conn.prepareStatement(sql);
//改动:4 给占位符传值
stmt.setString(1,loginName);
stmt.setString(2,loginPwd);
//4.执行sql语句(主要执行DQL、DML……)
rs = stmt.executeQuery(); //专门执行DQL查询语句
//5.处理查询结果集(只有当第4步执行的是select语句时,才有这第5步)
if(rs.next()){
flag = true;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//6.释放资源(使用完资源后一定要关闭资源,java和数据库属于进程间通信,开启后一定要关闭)
try{
if(stmt != null)
stmt.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try{
if(conn != null)
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try{
if(rs != null)
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
return flag;
}
/**
* 初始化用户界面
* @return 用户输入用户名、密码等信息
*/
private static Map<String, String> inintUI () {
Scanner s = new Scanner(System.in);
System.out.print("用户名:");
String loginName = s.nextLine();
System.out.print("密码:");
String loginPwd = s.nextLine();
Map<String, String> userLoginInfo = new HashMap<>();
userLoginInfo.put("loginName",loginName);
userLoginInfo.put("loginPwd",loginPwd);
return userLoginInfo;
}
}
3.对比Statement 与 PreparedStatement
3.1.区别:
-
statement存在SQL注入问题,PreparedStatement解决了SQL注入问题
-
Statement是编译一次执行一次,PreparedStatement是编译一次,可执行N次,效率较高
-
PreparedStatement会在编译阶段做类型的安全检查
-
综上:99%的情况使用PreparedStatement,1%的情况使用Statement
3.2.什么情况使用Statement?
- 业务要求必须进行SQL注入时
- Statement支持注入,凡是要求需要进行sql语句拼接的,必须使用Statement
3.2.1. 比如::::把emp表内的数据按工资
- desc排列
- asc 排列
Statement实现:
public class JDBCStatementOnly {
public static void main(String[] args) {
//JDBC代码
Connection conn = null;
//改动1:
Statement stmt = null;
// PreparedStatement stmt = null;
ResultSet rs = null;
//排列方式:
Scanner s = new Scanner(System.in);
System.out.print("desc or asc ?:");
String order = s.nextLine();
try{
//1.注册驱动(作用:告诉java程序,即将要连接哪个品牌的数据库)
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接(表示JVM的进程和数据库进程之间的 通道 打开了,使用完之后 必须关闭)
String url = "jdbc:mysql://localhost:3306/bjpowernode"; //自己的地址链接
String user = "root";
String password = "333";
conn = DriverManager.getConnection(url,user,password);
//3.获取数据库操作对象(专门执行sql语句的对象)
stmt = conn.createStatement();
//4.执行sql语句(主要执行DQL、DML……)
String sql = " select * from emp order by sal "+order;
//5.处理查询结果集(只有当第4步执行的是select语句时,才有这第5步)
rs = stmt.executeQuery(sql); //专门执行DQL查询语句
boolean flag = rs.next();
while (flag){
//列表标签
String empno = rs.getString("empno");
String ename = rs.getString("ename");
String sal = rs.getString("sal");
System.out.println(empno+","+ename+","+sal);
flag = rs.next();
}
}catch (Exception e){
e.printStackTrace();
}finally { //为保证一定关闭,放在finally关闭
//6.释放资源(使用完资源后一定要关闭资源,java和数据库属于进程间通信,开启后一定要关闭)
//按照从小到大的顺序关闭
try{
if(stmt != null)
stmt.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try{
if(conn != null)
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try{
if(rs != null)
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
preparedStatement 无法实现需求:
/***
* 把emp表内的数据按工资
* * desc排列
* * asc 排列
* preparedStatement 无法实现需求
*/
class JDBCpreparedStatementOnly {
public static void main(String[] args) {
//JDBC代码
Connection conn = null;
//改动1:
PreparedStatement stmt = null;
ResultSet rs = null;
//排列方式:
Scanner s = new Scanner(System.in);
System.out.print("desc or asc ?:");
String order = s.nextLine();
try{
//1.注册驱动(作用:告诉java程序,即将要连接哪个品牌的数据库)
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接(表示JVM的进程和数据库进程之间的 通道 打开了,使用完之后 必须关闭)
String url = "jdbc:mysql://localhost:3306/bjpowernode"; //自己的地址链接
String user = "root";
String password = "333";
conn = DriverManager.getConnection(url,user,password);
//3.获取数据库操作对象(专门执行sql语句的对象)
String sql = " select * from emp order by sal ? ";
stmt = conn.prepareStatement(sql);
//改动:4 给占位符传值
stmt.setString(1,order);
//4.执行sql语句(主要执行DQL、DML……)
//5.处理查询结果集(只有当第4步执行的是select语句时,才有这第5步)
rs = stmt.executeQuery();
boolean flag = rs.next();
while (flag){
//列表标签
String empno = rs.getString("empno");
String ename = rs.getString("ename");
String sal = rs.getString("sal");
System.out.println(empno+","+ename+","+sal);
flag = rs.next();
}
}catch (Exception e){
e.printStackTrace();
}finally { //为保证一定关闭,放在finally关闭
//6.释放资源(使用完资源后一定要关闭资源,java和数据库属于进程间通信,开启后一定要关闭)
//按照从小到大的顺序关闭
try{
if(stmt != null)
stmt.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try{
if(conn != null)
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try{
if(rs != null)
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
3.3.为什么有些情况Statement可以,PreparedStatement不行????
- 以3.2为例:把emp表内的数据按工资升序、降序排列
- 正常sql语句应该是:
-
select empno ,ename,sal from emp order by sal desc;//降序
-
select empno ,ename,sal from emp order by sal asc; //升序
- Statement程序:
-
//4.执行sql语句(主要执行DQL、DML……) String sql = " select * from emp order by sal "+order; System.out.println(sql);
- PreparedStatement程序:
-
//3.获取数据库操作对象(专门执行sql语句的对象) String sql = " select * from emp order by sal ? "; //预编译 stmt = conn.prepareStatement(sql); //给占位符传值 stmt.setString(1,order); System.out.println(sql);
4.PreparedStatement的CRUD
4.1.增insert:向表中插入数据——(no=14,name=Bob,class_id=2020)
-
//增 //insert into t_stu(no,name,class_id) values (14,'Bob',2020); String sql = "insert into t_stu(no,name, class_id ) values (?,?,?)"; ps = conn.prepareStatement(sql); // 给占位符传值 ps.setInt(1,14); ps.setString(2,"Bob"); ps.setInt(3,2020); ps.executeUpdate();
4.2.删delete:删除no>20的全部数据
-
//删 delete from t_stu where no>20; String sql3 = "delete from t_stu where no>?"; ps = conn.prepareStatement(sql3); // 给占位符传值 ps.setInt(1,20); ps.executeUpdate();
4.3.改update:no=20的数据,name都改为Anna
-
// //改 update t_stu set name='Anna' where no = 20 ; String sql2 = "update t_stu set name= ? where no=?"; ps = conn.prepareStatement(sql2); // 给占位符传值 ps.setString(1,"Anna"); ps.setInt(2,20); ps.executeUpdate();
4.4.查retrieve:查询no>10的所有数据
-
//查 select * from t_stu where no>10; String sql4 = " select * from t_stu where no>?"; ps = conn.prepareStatement(sql4); // 给占位符传值 ps.setInt(1,10); //4.执行sql语句(主要执行DQL、DML……) rs = ps.executeQuery(); //专门执行DQL查询语句
/**
* 演示preparedStatement 的 CRUD
*/
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.*;
public class preparedStatement {
public static void main(String[] args) {
//JDBC代码
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
//1.注册驱动(作用:告诉java程序,即将要连接哪个品牌的数据库)
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接(表示JVM的进程和数据库进程之间的 通道 打开了,使用完之后 必须关闭)
String url = "jdbc:mysql://localhost:3306/bjpowernode"; //自己的地址链接
String user = "root";
String password = "333";
conn = DriverManager.getConnection(url,user,password);
//3.获取数据库操作对象(专门执行sql语句的对象)
//一个问号表示一个占位符
//增 insert into t_stu(no,name, class_id ) values (?,?,?)
String sql = "insert into t_stu(no,name, class_id ) values (?,?,?)";
//insert into t_stu(no,name,class_id) values (20,'James',2020);
ps = conn.prepareStatement(sql);
// 给占位符传值
ps.setInt(1,14);
ps.setString(2,"Bob");
ps.setInt(3,2020);
ps.executeUpdate();
// 删 delete from t_stu where no=30;
String sql3 = "delete from t_stu where no>?";
ps = conn.prepareStatement(sql3);
// 给占位符传值
ps.setInt(1,20);
ps.executeUpdate();
// //改 update t_stu set name='dior' where no > ?;
String sql2 = "update t_stu set name= ? where no=25?";
ps = conn.prepareStatement(sql2);
// 给占位符传值
ps.setString(1,"Anna");
ps.setInt(2,20);
ps.executeUpdate();
//查
String sql4 = " select * from t_stu where no>?";
ps = conn.prepareStatement(sql4);
// 给占位符传值
ps.setInt(1,10);
//程序执行到此,会发送sql语句”框子“给DBMS,然后它进行‘预编译
//4.执行sql语句(主要执行DQL、DML……)
rs = ps.executeQuery(); //专门执行DQL查询语句
//5.处理查询结果集(只有当第4步执行的是select语句时,才有这第5步)
boolean flag = false;
flag = rs.next();
while (flag){
//列表标签
String no = rs.getString("no");
String name = rs.getString("name");
String class_id = rs.getString("class_id");
System.out.println(no+","+name+","+class_id);
flag = rs.next();
}
}catch (Exception e){
e.printStackTrace();
}finally {
//6.释放资源(使用完资源后一定要关闭资源,java和数据库属于进程间通信,开启后一定要关闭)
try{
if(ps != null)
ps.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try{
if(conn != null)
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try{
if(rs != null)
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}