一、在JDBC 连接数据库方面存在一个问题:SQL注入(安全隐患)
SQL注入即是指应用程序对用户输入数据的合法性没有判断或过滤不严,一些非法攻击者可以在应用程序中事先定义好的查询语句的结尾上添加额外的,导致在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息。
比如在我们用户登录 的时候,使用到了以下的登录密码:
/*用户名:sa 密码:ds' or '1'='1 登录成功*/ //select * from t_user where loginname='sa'and loginpw='ds' or '1'='1' 这里的密码使用到了SQL语句关键字:or,数据库服务器把它当作了sql语句来执行,导致用户登陆成功。这种现象被称为sql注入(黑客常用)
SQl注入安全隐患的根本原因是:
用户输入的信息中含有sql语句的关键字并且这些关键字参与sql语句的编译过程导致sql语句的意愿被扭曲,进而达到sql注入。
二、解决SQL注入问题的方案:
只要用户提供的信息不参与sql语句的编译过程,问题就解决了。 即使用户提供的信息中含有sql语句的关键字,但是没有参与编译,仍然不起作用 。
要想用户信息不参与SQL语句的编译,那么必须要使用java.sql.PreparedStatement
PreparedStatement接口继承了java.sql.Statement
PreparedStatement是属于预编译的数据库对象
PreparedStatement的原理是:预先对SQL语句的框架进行编译。然后再给SQL语句传“值”
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
/*解决SQL注入问题:
* 测试结果:
* 用户名:sa
密码:ds' or '1'='1
登录失败
*/
public class JDBCtest {
public static void main(String[] args) {
//初始化一个界面
Map<String, String> userloginInfo = initUI();
//验证用户名和密码
Boolean loginSuccess = login(userloginInfo);
System.out.println(loginSuccess?"登录成功":"登录失败");
}
/**
* 用户登录
* @param userLogin 用户登录信息
* @return false表示失败,true表示成功,
*/
private static Boolean login(Map<String, String> userLogin) {
//标识变量
Boolean flag =false;
//JDBC代码
ResultSet rs = null;
PreparedStatement ps = null;
Connection conn = null;
//单独定义变量
String loginname = userLogin.get("loginname");//获取用户输入的Map集合里面的“key”
String loginpw = userLogin.get("loginpw");
try {
//1、注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/sql", "root", "123456");
//3获取预编译的数据库操作对象
//SQL语句的框架这,两个?我们称为”占位符“,一个?表示一个占位符,一个?只能接受一个值,注意:?不能用单引号括起来。
String dml = "select * from t_user where loginname=? and loginpw=?";
//程序执行到此处,会发送SQL语句框架给DBMS,然后DBMS会进行对SQL语句的预编译
ps = conn.prepareStatement(dml);
//编译完成之后,就要对SQL语句“占位符?”进行传值(只编译一次)
//(第一个?下标是1,第二个?下标是2,JDBC中所有的下标都是从1开始的.
ps.setString(1,loginname);
ps.setString(2,loginpw);
//4、执行sql
rs = ps.executeQuery();
//5处理结果集
if (rs.next()){
flag= true;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//6释放资源
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (ps!= null) {
ps.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
return flag;
}
}
/**
* 初始化用户界面
* @return 输入用户名和密码等信息;
*/
private static Map<String, String> initUI () {
Scanner s = new Scanner(System.in);
System.out.print("用户名:");
String loginname = s.nextLine();
System.out.print("密码:");
String loginpw = s.nextLine();
//把用户名和密码组装到Map集合中去:
Map<String, String> userloginMap = new HashMap<>();
userloginMap.put("loginname", loginname);
userloginMap.put("loginpw", loginpw);
return userloginMap;
}
}
三、Statement和preparedStatement的对比
Statement存在SQl注入问题,preparedStatement解决了该问题
Statement是编译一次执行一次,preparedStatement是编译一次可执行N次,preparedStatement效率较高
preparedStatement会在编译阶段做类型的安全检查。
综上所述,preparedStatement使用较多,Statement极少数的情况下才使用。
2、那么什么情况下必须使用Statement呢?
就是业务方面必须要求支持sql注入的时候。,进行SQl语句拼接的,必须使用Statement。
例如:以下的程序给表中的数据排序,要用到SQL语句的拼接。
import java.sql.*;
import java.util.Scanner;
public class JDBCtest {
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
System.out.println("输入desc或asc,desc表示降序,asc表示升序:");
String d = s.nextLine();
//JDBC代码
ResultSet rs = null;
Statement statement = null;
Connection conn = null;
try {
//1、注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/sql", "root", "123456");
//3获取数据库操作对象
statement = conn.createStatement();
//4、执行sql
String dml = "select username from t_student order by username "+d;
rs = statement.executeQuery(dml);
//5处理结果集
while (rs.next()) {
System.out.println(rs.getString("username"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//6释放资源
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (statement != null) {
statement.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
输出结果:
/*输入desc或asc,desc表示降序,asc表示升序:
asc
jack
ls
rose
zs
*/
好了,以上就是我对SQL注入问题的解决方案,与大家一起共享,希望对大家可能有一定的帮助
你的问题得到解决了吗?欢迎在评论区留言,提出更好的意见和建议 。