JDBC_03实现用户登录业务并解决SQL注入

JDBC_03实现用户登录业务并解决SQL注入

1.需求描述

  • 1.需求:
    • 模拟用户登录功能。
  • 2.业务描述:
    • 程序运行的时候,提供一个输入的入口,可以让用户输入用户名和密码。
    • 用户输入用户名和密码后,提交信息,java程序收集到用户信息。
    • java程序连接数据库验证用户名和密码是否正确。
      • 正确:显示登录成功。
      • 错误:显示登录失败。
  • 3.数据的准备:
    • 在实际开发中,表的设计会使用专业的建模工具:PowerDesigner。
    • 使用该工具来进行数据库表的设计。

2.准备数据

1.打开PowerDesigner,新建一个Model,然后选第二个Model types,在右边方框里选PhySical Data Model。

在下方Model name改为t_user,DBMS选自己装的版本。然后ok。

2.在新界面找到右上方里的Table图表,选择该图表后,找一片空白地方点击,就创建了一张表。

3.Name字段不是真正创建的表名,只是一个代称,下面的Code才是真正要创建的表名。

4.表结构设计如下:

5.点击确定即可保存,再次双击表打开,查看最后一栏,已自动生成建表sql语句。

6.ctrl+s保存建表项目数据;在建表sql语句界面页中点左上角的保存按钮保存sql文件。

7.在导出的sql文件中添加插入语句以及完善一些细节

drop table if exists t_user;

/*==============================================================*/
/* Table: t_user                                                */
/*==============================================================*/
create table t_user
(
   id                   bigint auto_increment,
   loginName            varchar(255),
   loginPwd             varchar(255),
   realName             varchar(255),
   primary key (id)
);
insert into t_user(loginName,loginPwd,realName) values('zhangsan','123456','张三');
insert into t_user(loginName,loginPwd,realName) values('lisi','123456','李四');
commit;
select * from t_user;

8.将sql文件导入数据库

在开发时,sql文件可能很大,使用一些工具无法导入数据库中。此时可以打开dos窗口,进入数据库,使用“source + sql文件路径” 导入sql文件。

因为dos窗口支持的编码格式是gbk,所以中文显示会乱码,但不碍事儿,数据已经成功导入数据库中。

至此,前期的准备工作已完成,可以进行程序设计了。

3.设计程序

3.1整体程序设计思路

本程序分为两部分,分别为初始化登录界面部分,以及验证用户名和密码部分。

将两个部分写进两个方法里实现。

用户在初始化界面方法里输入用户名和密码,用户信息被放进一个Map对象并返回。

将返回的Map对象传进验证用户名和密码方法中,使用jdbc连接数据库,查询用户表信息,

将Map对象中的用户信息和用户表中的信息进行比对,如果匹配就返回true,如果不匹配就返回false。

package com.tsccg.jdbc;

import java.util.Map;

/**
 * @Author: TSCCG
 * @Date: 2021/07/27 17:01
 *
 */
public class JdbcLogin {
    /**
     * 程序入口
     */
    public static void main(String[] args) {
        //初始化界面
        Map<String,String> userInfo  = initUi();
        //验证用户信息是否正确
        boolean loginResule = login(userInfo);
        System.out.println(loginResule ? "登录成功" : "登录失败");
    }
    /**
     * 初始化登录界面
     * @return 返回一个Map对象,包含用户名及用户密码
     */
    private static Map<String,String> initUi() {
        return null;
    }

    /**
     * 登录,验证用户名以及用户密码
     * @param userInfo 用户信息
     * @return 用户信息正确就返回true,不正确就返回false
     */
    private static boolean login(Map<String, String> userInfo) {
        return false;
    }
}

3.2初始化登录界面

用Scanner接收用户键盘输入,然后将用户信息放进Map集合里,并返回。

/**
 * 初始化登录界面
 * @return 返回一个Map对象,包含用户名及用户密码
 */
private static Map<String,String> initUi() {
    Scanner sc = new Scanner(System.in);

    System.out.print("用户名:");
    //nextLine()读一行
    String username = sc.nextLine();
    System.out.print("用户密码:");
    String password = sc.nextLine();
    //将用户信息放进Map集合
    Map<String,String> userInfo = new HashMap<>();
    userInfo.put("loginName",username);
    userInfo.put("loginPwd",password);

    return userInfo;
}

3.3验证用户名和密码

通过jdbc查询出有该用户名和密码的那条记录,如果匹配,那么查询结果就有数据。故只需调用next()方法即可判断是否匹配。

/**
 * 登录,验证用户名以及用户密码
 * @param userInfo 用户信息
 * @return 用户信息正确就返回true,不正确就返回false
 */
private static boolean login(Map<String, String> userInfo) {
    boolean isSuccess = false;
    Connection conn = null;
    Statement stmt = null;
    ResultSet rs = null;
    try {
        //1.注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        //2.获取连接
        String url = "jdbc:mysql://localhost:3306/tsccg";
        String user = "root";
        String password = "123456";
        conn = DriverManager.getConnection(url,user,password);
        //3.获取数据库操作对象
        stmt = conn.createStatement();
        //4.执行SQL语句
        //以用户名和密码为条件在用户表中查询该用户信息
        String sql = "select * from t_user where loginName = '"+ userInfo.get("loginName") +"' and loginPwd = '"+ userInfo.get("loginPwd") +"'";
        rs = stmt.executeQuery(sql);
        //5.处理查询结果集,
        //不需要遍历查询结果集,只要查询到数据就证明有该用户的信息
        if (rs.next()) {
            isSuccess = true;
        }
    } catch (SQLException | ClassNotFoundException e) {
        e.printStackTrace();
    } finally {
        //6.释放资源
        //先关闭rs
        try {
            if (rs != null) {
                rs.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        //再关闭stmt
        try {
            if (stmt != null) {
                stmt.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        //最后再关闭conn
        try {
            if (conn != null) {
                conn.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    return isSuccess;
}

3.4完整代码

package com.tsccg.jdbc;

import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

/**
 * @Author: TSCCG
 * @Date: 2021/07/27 17:01
 *
 */
public class JdbcLogin {
    /**
     * 程序入口
     */
    public static void main(String[] args) {
        //初始化界面
        Map<String,String> userInfo  = initUi();
        //验证用户信息是否正确
        boolean loginResule = login(userInfo);
        System.out.println(loginResule ? "登录成功" : "登录失败");
    }
    /**
     * 初始化登录界面
     * @return 返回一个Map对象,包含用户名及用户密码
     */
    private static Map<String,String> initUi() {
        Scanner sc = new Scanner(System.in);

        System.out.print("用户名:");
        //nextLine()读一行
        String username = sc.nextLine();
        System.out.print("用户密码:");
        String password = sc.nextLine();
        //将用户信息放进Map集合
        Map<String,String> userInfo = new HashMap<>();
        userInfo.put("loginName",username);
        userInfo.put("loginPwd",password);

        return userInfo;
    }

    /**
     * 登录,验证用户名以及用户密码
     * @param userInfo 用户信息
     * @return 用户信息正确就返回true,不正确就返回false
     */
    private static boolean login(Map<String, String> userInfo) {
        boolean isSuccess = false;
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        try {
            //1.注册驱动
            Class.forName("com.mysql.jdbc.Driver");
            //2.获取连接
            String url = "jdbc:mysql://localhost:3306/tsccg";
            String user = "root";
            String password = "123456";
            conn = DriverManager.getConnection(url,user,password);
            //3.获取数据库操作对象
            stmt = conn.createStatement();
            //4.执行SQL语句
            String sql = "select * from t_user where loginName = '"+ userInfo.get("loginName") +"' and loginPwd = '"+ userInfo.get("loginPwd") +"'";
            rs = stmt.executeQuery(sql);
            //5.处理查询结果集
            if (rs.next()) {
                isSuccess = true;
            }
        } catch (SQLException | ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            //6.释放资源
            //先关闭rs
            try {
                if (rs != null) {
                    rs.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            //再关闭stmt
            try {
                if (stmt != null) {
                    stmt.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            //最后再关闭conn
            try {
                if (conn != null) {
                    conn.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return isSuccess;
    }
}

运行:

用户名:zhangsan
用户密码:123456
登录成功
用户名:阿巴阿巴阿巴
用户密码:666
登录失败

4.演示SQL注入现象

上面编写的程序存在漏洞。

重新运行上面的程序,并输入如下用户名和密码进行登录:

用户名:abc
用户密码:abc'or'1'='1
登录成功

可见,虽然用户名和密码不正确,但仍然登录成功了。

这是什么原因呢?

我们debug运行一下:

如图所示,

原本的sql语句为:

String sql = "select * from t_user where loginName = '" + userInfo.get("loginName") +"' and loginPwd = '" + userInfo.get("loginPwd") +"'";

在输入

用户名:abc
用户密码:abc'or'1'='1

后,sql语句被改成了

select * from t_user where loginName = 'abc' and loginPwd = 'abc'or'1'='1'

利用or的优先级比and低,使得原先的and语句变为了or语句。

用户通过输入精心构造的字符串去更改SQL语句,这就叫SQL注入。

常见的SQL注入方法还有:

用户名:'or 1#
用户密码:123
登录成功

在输入'or 1#后,SQL语句将变为:

select * from t_user where loginName =''or 1#' and loginPwd = '123'

‘’or 1恒为true,#注释掉后面的内容,所以查询语句可以正确执行。

5.解决SQL注入

5.1导致SQL注入的根本原因

用户输入的信息中含有sql语句的关键字,并且这些关键字参与sql语句的编译过程,导致sql语句的原意被更改,从而达到sql注入。

所以,只要不让用户输入的内容参与编译,就可以解决SQL注入。

5.2解决SQL注入步骤

1.将Statement换成PreparedStatement

Connection conn = null;
//Statement stmt = null;
PreparedStatement ps = null;
ResultSet rs = null;

2.获取预编译的数据库操作对象

更改JDBC编程六步中的第3步:

//3.获取预编译的数据库操作对象
//sql语句的框子,其中?代表占位符,只能传入值。注意:不可用单引号把占位符括起来。
String sql = "select * from t_user where loginName = ? and loginPwd = ?";
//程序执行到此,会发送sql语句框子给DBMS,然后DBMS进行sql语句的预先编译。
//通过conn调用prepareStatement方法。注意:方法名prepare后面没有d
ps = conn.prepareStatement(sql);
//获取用户输入的用户名和用户密码
String loginName = userInfo.get("loginName");
String loginPwd = userInfo.get("loginPwd");
//给占位符?传值(第一个问号下标是1,第二个问号下标是2,JDBC中所有下标从1开始)
ps.setString(1,loginName);
ps.setString(2,loginPwd);

3.执行SQL语句

更改JDBC编程六步中的第4步:

//4.执行SQL语句
rs = ps.executeQuery();//不需要再次填入sql

至此,程序已更改完毕。

5.3.完整代码

package com.tsccg.jdbc.login;

import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

/**
 * @Author: TSCCG
 * @Date: 2021/07/28 14:37
 */
public class JdbcLoginDemo02 {
    /**
     * 程序入口
     */
    public static void main(String[] args) {
        //初始化界面
        Map<String,String> userInfo  = initUi();
        //验证用户信息是否正确
        boolean loginResule = login(userInfo);
        System.out.println(loginResule ? "登录成功" : "登录失败");
    }
    /**
     * 初始化登录界面
     * @return 返回一个Map对象,包含用户名及用户密码
     */
    private static Map<String,String> initUi() {
        Scanner sc = new Scanner(System.in);

        System.out.print("用户名:");
        //nextLine()读一行
        String username = sc.nextLine();
        System.out.print("用户密码:");
        String password = sc.nextLine();
        //将用户信息放进Map集合
        Map<String,String> userInfo = new HashMap<>();
        userInfo.put("loginName",username);
        userInfo.put("loginPwd",password);

        return userInfo;
    }

    /**
     * 登录,验证用户名以及用户密码
     * @param userInfo 用户信息
     * @return 用户信息正确就返回true,不正确就返回false
     */
    private static boolean login(Map<String, String> userInfo) {
        boolean isSuccess = false;
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            //1.注册驱动
            Class.forName("com.mysql.jdbc.Driver");
            //2.获取连接
            String url = "jdbc:mysql://localhost:3306/tsccg";
            String user = "root";
            String password = "123456";
            conn = DriverManager.getConnection(url,user,password);
            //3.获取预编译的数据库操作对象
            //sql语句的框子,其中一个?代表一个占位符,只能传入值。注意:不可用'?'把占位符括起来
            String sql = "select * from t_user where loginName = ? and loginPwd = ?";
            //程序执行到此,会发送sql语句框子给DBMS,然后DBMS进行sql语句的预先编译。
            ps = conn.prepareStatement(sql);
            //获取用户输入的用户名和用户密码
            String loginName = userInfo.get("loginName");
            String loginPwd = userInfo.get("loginPwd");
            //给占位符?传值(第一个问号下标是1,第二个问号下标是2,JDBC中所有下标从1开始)
            ps.setString(1,loginName);
            ps.setString(2,loginPwd);
            //4.执行SQL语句
//            String sql = "select * from t_user where loginName = '" + userInfo.get("loginName") +"' and loginPwd = '" + userInfo.get("loginPwd") +"'";
            //不需要再次填入sql
            rs = ps.executeQuery();
            //5.处理查询结果集
            if (rs.next()) {
                isSuccess = true;
            }
        } catch (SQLException | ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            //6.释放资源
            //先关闭rs
            try {
                if (rs != null) {
                    rs.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            //再关闭ps
            try {
                if (ps != null) {
                    ps.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            //最后再关闭conn
            try {
                if (conn != null) {
                    conn.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return isSuccess;
    }
}

5.4测试

使用SQL注入的方式测试程序

用户名:abc
用户密码:abc'or'1'='1
登录失败
posted @ 2021-07-27 21:43  TSCCG  阅读(115)  评论(0编辑  收藏  举报