1、 准备 - 模拟用户登录案例

数据库信息:

实现代码:

package com.edut.cn.tarena.jdbc;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;

import com.edut.cn.tarena.util.JdbcUtil;

/** * 模拟用户登录案例 */
public class LoginUser {
	public static void main(String[] args) {
		/* * 1. 提示用户登录,并提示用户输入:用户名、密码 * 2. 接收用户输入的用户名和密码 * 3. 根据用户名和密码查询jt_db.user表 * 4. 如果能够查询到记录,则说明用户名密码正确,允许登录(模拟) * 5. 如果查不到记录,则说明用户名或密码不正确,则提示失败! */
		//1. 提示用户登录,并提示用户输入:用户名、密码
		Scanner sc = new Scanner(System.in);
		//2. 接收用户输入的用户名和密码
		System.out.println("请登录...");
		System.out.print("请输入用户名:");
		String username = sc.nextLine();
		System.out.print("请输入密码:");
		String password = sc.nextLine();
		
		//3. 根据用户名和密码查询jt_db.user表
		if(isLogin(username,password)) {
			//4. 如果能够查询到记录,则说明用户名密码正确,允许登录(模拟)
			System.out.println("允许登录(模拟)!");
		}else {
			//5. 如果查不到记录,则说明用户名或密码不正确,则提示失败!
			System.out.println("用户名或密码不正确,登录失败...");
		}
	}

	private static boolean isLogin(String username, String password) {
		Connection conn = null; 
		Statement stat = null;
		ResultSet rs = null;
		try {
			conn = JdbcUtil.getConn();
			stat = conn.createStatement();
			String sql = "select * from user "
					+ " where username='"+username+"' and password='"+password+"' ; ";
			rs = stat.executeQuery(sql);
			while (rs.next()) {
				String name = rs.getString("username") ;
				String pass = rs.getString("password") ;
				System.out.println("username="+name+",password="+pass);
				return true;
			}
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			JdbcUtil.close(conn, stat, rs);
		}
		
		return false;
	}
}

测试结果:


<mstyle mathcolor="&#35;ff0011"> B u g </mstyle> \color{#ff0011}{** Bug **} Bug

问题描述:

然而上面的功能,有一个大Bug。

当输入“用户名”如下情况:<mark>用户名,密码均错误,但仍然登录成功。</mark>

请登录…
请输入用户名:张飞’#’
请输入密码:1
username=张飞,password=123
允许登录(模拟)!

请登录…
请输入用户名:张飞’ or '1=1
请输入密码:sadfsaf
username=张飞,password=123
允许登录(模拟)!


问题分析1:

由于特殊字符’<mark>#</mark>'的输入,#后的语句改变,变为了注释。

<mark>select * from user where username=‘张飞’</mark>#’’ and password=‘1’ ;

后台的sql查询语句成了:黄色那一块

select * from user where username=‘张飞’


问题分析2:

select * from user where username=‘张飞’ or ‘1=1’ and password=‘sadfsaf’ ;

语句语义变了。


2、 SQL注入攻击

通过上面的案例,我们发现在执行时,<mark>不输入密码只输入用户名也可以登陆成功</mark>。

这就是SQL注入攻击。

SQL注入攻击:

  • 由于后台的SQL语句是拼接而来的。
  • 其中的参数是由用户提交的,
  • <mark>如果用户在提交参数时,在其中掺杂了一些SQL关键字或者特殊符号,就可能会导致SQL语句的语意发生变化。</mark>
  • 从而执行一些意外的操作。

3、 防止SQL注入攻击

如何防止SQL注入攻击?

  • 使用正则表达式对用户提交的参数进行校验。
  • 使用PreparedStatement对象来替代Statement对象。

<mark>下面通过第二种方式解决SQL注入攻击</mark>:

  • 添加loginByPreparedSatement方法,
  • 在方法中,使用PreparedStatement来代替Statement作为传输器对象使用!

4、 PreparedStatement

在上面的增删改查的操作中,使用的是Statement传输器对象。
而在开发中我们用的更多的传输器对象是PreparedStatement对,

<mark>PreparedStatement是Statement的子接口,相比Statement:</mark>

  • <mark>更安全</mark>
  • <mark>更高效</mark>

下面实现:

  • 添加loginByPreparedSatement方法
  • 在方法中,使用 PreparedStatement 来代替 Statement 作为传输器对象使用

package com.edut.cn.tarena.jdbc;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;

import com.edut.cn.tarena.util.JdbcUtil;

/** * 模拟用户登录案例 */
public class LoginUser {
	public static void main(String[] args) {
		/* * 1. 提示用户登录,并提示用户输入:用户名、密码 * 2. 接收用户输入的用户名和密码 * 3. 根据用户名和密码查询jt_db.user表 * 4. 如果能够查询到记录,则说明用户名密码正确,允许登录(模拟) * 5. 如果查不到记录,则说明用户名或密码不正确,则提示失败! */
		//1. 提示用户登录,并提示用户输入:用户名、密码
		Scanner sc = new Scanner(System.in);
		//2. 接收用户输入的用户名和密码
		System.out.println("请登录...");
		System.out.print("请输入用户名:");
		String username = sc.nextLine();
		System.out.print("请输入密码:");
		String password = sc.nextLine();
		
		//3. 根据用户名和密码查询jt_db.user表
		if(isLogin(username,password)) {
			//4. 如果能够查询到记录,则说明用户名密码正确,允许登录(模拟)
			System.out.println("允许登录(模拟)!");
		}else {
			//5. 如果查不到记录,则说明用户名或密码不正确,则提示失败!
			System.out.println("用户名或密码不正确,登录失败...");
		}
	}

	private static boolean isLogin(String username, String password) {
		Connection conn = null; 
		Statement stat = null;
		ResultSet rs = null;
		try {
			conn = JdbcUtil.getConn();
			/* * 修改传输器 */
			//stat = conn.createStatement();
			//String sql = "select * from user "+ " where username='"+username+"' and password='"+password+"' ; ";
			//rs = stat.executeQuery(sql);
			/* * 这里在获取传输器时,先把sql语句的骨架发送给数据库编译 * 并确定下来, * 后面发送的只能是sql参数,并不能改变sql语句的骨架。 */
			String sql ="select * from user where username=? and password=? ; "; 
			PreparedStatement ps = conn.prepareStatement(sql); //获取传输器
			ps.setString(1, username);//设置参数1
			ps.setString(2, password);//设置参数2
			rs = ps.executeQuery();
			if (rs.next()) {
				String name = rs.getString("username") ;
				String pass = rs.getString("password") ;
				System.out.println("username="+name+",password="+pass);
				return true;
			}
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			JdbcUtil.close(conn, stat, rs);
		}
		
		return false;
	}
}

再次执行程序,按照上面的操作登录。此时,已经成功的防止了SQL注入攻击问题了。


总结

<mark>使用PreparedStatement对象可以防止SQL注入攻击</mark>
而且通过方法设置参数更加的方便且不易出错!
还可以从某些方面提高程序执行的效率!