JDBC实现用户登录时出现SQL注入问题
JDBC实现用户登录:
public class jdbcTest06{
public satic void main(String[] args){
Map<String,String> userLoginInfo initUI();
boolean loginSuccess = login(userLoginInfo);
System.out.println(loginSuccess ? "登录成功" : "登录失败");
}
private static boolean login(Map<String,String> userLoginInfo){
boolean loginSuccessornot = false;
String loginName = userLoginInfo.get("loginName");
String loginPwd = userLoginInfo.get("loginPwd");
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try{
// 1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2. 获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode","root","333");
// 3. 获取数据库操作对象
stmt = conn.creatStatement();
// 4. 执行SQL语句
String sql = "select * from t_user where loginName '"+loginName+"' and '"+loginPwd+"'";
rs = stmt.executeQuery(sql);
// 5. 处理结果集
if(rs.next()){
loginSuccessornot = true;
}else{
loginSuccessornot = false;
}
}catch(SQLException e){
e.printStackTrace();
}finally{
//释放资源
if(rs != null){try{rs.close();}catch(SQLException e){e.printStackTrace();}}
if(stmt != null){try{stmt.close();}catch(SQLException e){e.printStackTrace();}}
if(conn != null){try{conn.close();}catch(SQLException e){e.printStackTrace();}}}
return loginSuccessornot;
}
private static Map<String,String> iniUT(){
Scanner s = new Scanner(System.in);
System.out.println("用户名:");
String loginName = s.nextLine();
System.out.println("密码:");
String loginPwd = s.nextLine();
Map<String,String> userLoginInfo = new HaspMap<>();
userLoginInfo.put("loginName","loginName");
userLoginInfo.put("loginPwd","loginPwd");
return userLoginInfo;
}
}
SQL注入
当前程序存在问题:
输入:
用户名:fdsa
密码:fdsa’ or ‘1’ = ‘1
输入:
登录成功 ( 数据库中不存在这条数据 )
这种现象被成为SQL注入
问题代码块:
jade,mysql
Statement stmt = null;
// 3. 获取数据库操作对象
stmt = conn.creatStatement();
// 4. 执行SQL语句
String sql = "select * from t_user where
'"+loginName+"' and '"+loginPwd+"'";
rs = stmt.executeQuery(sql);
以上代码块解析:
获取数据库操作对象 stmt 后执行SQL语句将传了loginName 和 loginPwd 两个参数的SQL语句作为参数传给了 executeQuery() 方法
然后 executeQuery() 方法进行编译,就会把 fdsa’ or ‘1’ = ‘1 作为sql语句一起编译了,
看看编译后的sql语句:
sql: "select * from t_user where loginName = 'fdsa' and loginPwd = 'fdsa' or '1' = '1'"
可以看到查询语句的where条件变成了
loginName = 'fdsa' and loginPwd = 'fdsa' or '1' = '1'"
意思就是 where(loginName = 'fdsa' and loginPwd = 'fdsa')
或者 ('1' = '1')
而'1' = '1'
恒成立,所以无论怎么查询都能查到结果,甚至是将所有的用户账号密码都查了出来,最后完成登录
所以出现SQL注入的关键是:
用户输入的非法信息被数据库操作系统编译了,例如用户输入了含有SQL语句的关键字,并且这些关键字参与了SQL语句的编译过程
解决SQL注入
只要用户提供的信息不参与SQL编译过程,即使提供的信息含有关键字等非法信息,但是只要不参加数据库操作系统的编译九不会形成注入
要想用户信息不参与SQL语句的编译,那必须适合用java.sql.PreparedStatement
PreparedStatement 接口集成了java.sql.Statement,PreparedStatement 是属于预编译的数据库操作对象
PreparedStatement 的原理是预先对SQL语句的框架进行编译,然后再给SQL语句传值
解决SQL注入的关键是:
用户提供的信息中即使含有sql语句的关键字,但是这些关键字并没有参与编译,不起作用
解决注入代码:
package com.hyqwsq.jdbc;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
/*
实现功能:
1. 需求:模拟用户登录功能的实现
2. 业务描述:
程序运行的时候提供一个数据的入口,可以让用户输入用户名和密码,
用户数据用户名和米啊后,提交信息,Java程序收集到用户信息
Java程序连接数据库验证用户名是否合法
合法:显示登录成功
不合法:显示登录失败
3. 数据准备
在实际开发中,表的设计会使用专业的建模工具,我用的使PoerDesigner,
使用PD工具进行数据库表的设计,(附件user-login.sql脚本);
4. 当前程序存在问题:
用户名:fdsa
密码:fdsa' or '1' = ‘1
登录成功
这种现象被成为SQL注入
5. 导致SQL注入的根本原因:
将密码:fdsa' or '1' = ‘1传进去后的SQL语句:
sql: "select * from t_user where loginName = 'fdsa' and loginPwd = 'fdsa' or '1' = '1'"
查询条件变成了:where(loginName = 'fdsa' and loginPwd = 'fdsa') or ('1' = '1')
前面的用户名和用户密码不用对,后面的’1‘ = ’1‘恒成立,导致这条SQL语句一定能查到数据,所以一定可以登录成功
原因:用户输入的非法信息被数据库操作系统编译了,例如用户输入了含有SQL语句的关键字,并且这些关键字参与了SQL语句的编译过程
导致SQL语句的原意被扭曲,进而形成SQL注入
6. 解决SQL注入:
只要用户提供的信息不参与SQL编译过程
即使提供的信息含有关键字等非法信息,但是只要不参加数据库操作系统的编译九不会形成注入
要想用户信息不参与SQL语句的编译,那必须适合用java.sql.PreparedStatement
PreparedStatement 接口集成了java.sql.Statement
PreparedStatement 是属于预编译的数据库操作对象
PreparedStatement 的原理是预先对SQL语句的框架进行编译,然后再给SQL语句传值
7. 解决SQL注入的关键是:
用户提供的信息中即使含有sql语句的关键字,但是这些关键字并没有参与编译,不起作用
*/
public class jdbcTest06 {
public static void main(String[] args) {
// 初始化一个界面,创建一个可以初始化界面的方法,
// 接收的返回值是用户名和密码,放到一个MAP容器中
Map<String,String> userLoginInfo = initUI();
// 验证用户和密码并登录,创建登录方法,传进用户信息,userLoginInfo为参数
// 登录失败或者成功只有两种结果,所以方法返回一个布尔类型就好
boolean loginSuccess = login(userLoginInfo);
// 最后输出结果
System.out.println(loginSuccess ? "登录成功" : "登录失败");
}
/**
* 用户登录
* @param userLoginInfo 用户登录信息
* @return false 表示失败,true 表示成功
*/
private static boolean login(Map<String, String> userLoginInfo) {
// 打标记
boolean loginSuccessornot = false;
// 单独定义变量
String loginName = userLoginInfo.get("loginName");
String loginPwd = userLoginInfo.get("loginPwd");
//JDBC代码
Connection conn = null;
// Statement stmt = null;
// 换成预编译的数据库操作对象 PrepareSratement
PreparedStatement ps = null;
ResultSet rs = null;
try {
// 1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2.获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/hyqwsq","root","13903014064");
// 3.获取编译的数据库操作对象
// 将原来的值换成占位符 ?
// 这样sql语句变成了框架,? 表示占位符,一个占位符将接收一个值,占位符不能使用单引号括起来
String sql = "select * from t_user where loginName = ? and ?";
// 程序执行到这里,会发送SQL语句框架给DBMS,然后DBMS进行SQL语句的预先编译
ps = conn.prepareStatement(sql);
// 给占位符 ? 传值(第一个占位符的下标是1, 第二个占位符的下标是2,JDBC中所有下标从1开始)
ps.setString(1,loginName);
ps.setString(2,loginPwd);
// getString()方法传进去字符串后会自动加上单引号,如果是其getInt()就不会
// 4.执行SQL
//把用户数据的用户名的用户密码的字符串拼到字符串中:双引号中间加两个加号
//String sql = "select * from t_user where loginName = '"+userLoginInfo.get("loginName")+"' and loginPwd = '"+userLoginInfo.get("loginPwd")+"'";
//单独定义变量后
// String sql = "select * from t_user where loginName = '"+loginName+"' and '"+loginPwd+"'";
// 以上正好完成了SQL语句的拼接,以下代码的含义是:发送SQL语句给DBMS,DBMS进行编译
// 正好将用户提供的非法信息编译减去,导致了原来的SQL语句含义被扭曲了
rs = ps.executeQuery(sql);
// 5.处理结果集
// while(rs.next()){}
//不需要循环查看结果集,因为返回值为布尔类型,只需要看有没有结果集就好
if(rs.next()){
//登录成功
loginSuccessornot = true;
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}finally {
// 6.关闭通道,释放资源
if(rs != null){
try {rs.close();} catch (SQLException throwables) {throwables.printStackTrace();}}
if(ps != null){
try {ps.close();} catch (SQLException throwables) {throwables.printStackTrace();}}
if(conn != null){
try {conn.close();} catch (SQLException throwables) {throwables.printStackTrace();}}
}
return loginSuccessornot;
}
/**
* 初始化用户界面
* @return 返回用户输入的用户名和密码等登录信息
*/
private static Map<String, String> initUI() {
Scanner s = new Scanner(System.in);
//输入用户名
System.out.println("用户名:");
String loginName = s.nextLine();
// 输入密码
System.out.println("密码:");
String loginPwd = s.nextLine();
//将数据组装到嘛Map中
Map<String,String> userLoginInfo = new HashMap<>();
userLoginInfo.put("loginName","loginName");
userLoginInfo.put("loginPwd","loginPwd");
return userLoginInfo;
}
}