sql注入问题和PreparedStatement

简单登录

为了演示sql注入问题,我们使用jdbc来实现一个简单的登录功能,用户从控制台输入用户名和密码,然后从数据库中查找是否有匹配的数据,如果存在则登录成功,否则登录失败。

创建一个UserService类,该来主要是做用户相关的操作。

/*
用户服务
 */
public class UserService {

    public User selectByNameAndPassword(String username,String password) {
        String sql = "select id,name,password,email,birthday from t_user where name='" + username + "' and password='" + password + "'";
        System.out.println(sql);
        User u = null;
        try (
                //获取Connection对象
                Connection conn = DBUtil.getConnection();
                //获取Statement对象
                Statement stmt = conn.createStatement();
                //获取ResultSet对象
                ResultSet rs = stmt.executeQuery(sql)
        ) {
            //处理结果
            while (rs.next()) {
                u = new User();
                u.setId(rs.getInt("id"));
                u.setName(rs.getString("name"));
                u.setPassword(rs.getString("password"));
                u.setEmail(rs.getString("email"));
                u.setBirthday(rs.getDate("birthday"));

            }

        } catch (SQLException e) {
            e.printStackTrace();
        }
        return u;
    }
}

创建登录类

public class Login {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        System.out.println("请输入用户名:");
        String username = sc.nextLine();

        System.out.println("请输入密码:");
        String password = sc.nextLine();

        //创建UserService对象判断当前用户是否可以登录成功
        UserService us = new UserService();
     
        User user = us.selectByNameAndPassword(username, password);

        if (user == null) {
            System.out.println("用户名或密码不正确");
        }else {
            System.out.println("登录成功");
        }
    }
}

之后从控制台输入用户名和密码进行测试

sql注入问题

在之前写的登录功能中,随便输入一个用户名,在密码部分输入下面内容:

123' or 1='1

这个密码肯定是不正确的,但是依然能够登录成功,这个就是sql注入问题,也就是说之前写的登录功能有安全问题。为什么会导致这样的问题?我们将最终执行的sql打印可以看到是这样的:

select id,name,password,email,birthday from t_user where name='tb' and password='123' or 1='1';

出现问题的部分是 or 1=’1’这部分,因为无论如何这部分的运算结果都是true,所以在输错用户名和密码的情况下依然登录成功了。要想解决这个问题,可以使用jdbc提供的PreparedStatement。

PreparedStatement

PreparedStatement是一个接口,它继承了Statement,该接口有以下几个优点:

  • 性能比Statement高,会把sql预编译
  • 可以解决sql注入问题

使用PreparedStatement修改登录功能的代码:

package com.monkey1024.jdbc.service;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import com.monkey1024.jdbc.bean.User;
import com.monkey1024.jdbc.util.DBUtil;

public class LoginServiceNew {

	public User findUserByNameAndPassword(String name, String password) {
		User u = null;
		String sql = "select id,name,password,email,birthday from t_user where name=? and password=?";
		System.out.println(sql);
		// 获取连接Connection
		try (Connection conn = DBUtil.getConnection();
				PreparedStatement stmt = conn.prepareStatement(sql)) {
			stmt.setString(1, name);
			stmt.setString(2, password);

			try (// 执行sql语句,并返回结果
					ResultSet rs = stmt.executeQuery()) {
				// 处理结果
				while (rs.next()) {
					u = new User();
					u.setId(rs.getInt("id"));
					u.setName(rs.getString("name"));
					u.setPassword(rs.getString("password"));
					u.setEmail(rs.getString("email"));
					u.setBirthday(rs.getDate("birthday"));
				}

			}

		} catch (SQLException e) {
			e.printStackTrace();
		}

		return u;
	}
}

在sql语句中,使用?作为占位符来替代要传入的内容,通过调用PreparedStatement的setString等方法将要传入的内容作为参数传递过去。

使用PreparedStatement完成增删改的功能

增加

需要调用preparedStatement中的executeUpdate()方法,返回结果若是0,则表示添加失败

    public int addStudent(Student student) throws SQLException {
        String sql = "INSERT INTO t_student(name,password,email,birthday) VALUES (?,?,?,?)";

        try(Connection connection = DBUtil.getConnection();
            PreparedStatement preparedStatement  = connection.prepareStatement(sql)
        ) {
            preparedStatement.setString(1, student.getName());
            preparedStatement.setString(2, student.getPassword());
            preparedStatement.setString(3, student.getEmail());
            preparedStatement.setDate(4, student.getBirthday());
            int num = preparedStatement.executeUpdate();

            return num;
        }
    }

修改

修改操作也需要调用preparedStatement中的executeUpdate()方法

    public int updateStudent(Student student) throws SQLException {
        String sql = "UPDATE t_student set name=?,password=?,email=?,birthday=? where id=?;

        try(Connection connection = DBUtil.getConnection();
            PreparedStatement preparedStatement  = connection.prepareStatement(sql)
        ) {
            preparedStatement.setString(1, student.getName());
            preparedStatement.setString(2, student.getPassword());
            preparedStatement.setString(3, student.getEmail());
            preparedStatement.setDate(4, student.getBirthday());
            preparedStatement.setDate(5, student.getId());
            
            int num = preparedStatement.executeUpdate();

            return num;
        }
    }

删除

这里演示批量操作,比如要删除id是1,3,6,8的数据。在给?赋值结束后,需要调用preparedStatement.addBatch()方法,此时并不会执行sql语句,当调用preparedStatement.executeBatch()方法后会将之前的sql语句一块执行,返回的数组中若元素为0,则表示该数据删除失败

 public int[] deleteStudent(int[] ids) throws SQLException {
        String sql = "DELETE FROM t_student WHERE id=?";
        try (
                Connection connection = DBUtil.getConnection();
                PreparedStatement preparedStatement = connection.prepareStatement(sql);
        ) {

            for (int i = 0; i < ids.length; i++) {
                preparedStatement.setInt(1, ids[i]);
                //批量操作
                preparedStatement.addBatch();
            }
            
            //批量执行
            int[] arr = preparedStatement.executeBatch();

            return arr;

        }
    }

分页查询

比如数据库中有十万条学生数据,在实际应用中不可能把这些数据全部查询出来给用户查看,所以需要进行分页查询。比如在百度搜索的时候,每页显示的是10条结果(广告除外),如果想要看第11条的数据,就需要在网页中点击下方的第二页。

对于数据库查询语句来说,要进行分页操作时需要2个数据:

  • 页码(pageNum)
  • 每页显示的数据量(pageSize)

有了这两个数据就可以根据下面公式进行分页查询了

select * from t_student limit (pageNum - 1) * pageSize, pageSize

比如pageNum=3,pageSize=10,此时说明每页显示10条数据,要查询第3页的,即查询21~30的数据,套用上面公式:

select * from t_student limit 20, 10