解决JDBC实现用户登录时出现SQL注入问题详解

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;
    }


}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值