JDBC(增删改查)与SQL注入攻击、BeanUtils工具包使用

目    录(本篇字数:2841)

SQL注入攻击

测试 SQL 注入攻击

PreparedStament 化解 SQL 注入

JDBC 增、删、改、查

增、删、改方法

通用查询方法封装

(1)ResultSetMetaData 接口

(2)利用 sql 语句添加别名的方式

(3)利用 Java 反射给 User 类中的每个属性变量赋予值 .

(4)BeanUtils 工具包


  • SQL注入攻击

    SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 语句段或命令,从而利用系统的 SQL 引擎完成恶意行为的做法 。   

    SQL注入攻击,指对数据库进行攻击的手段之一。例如:在表单提交时候,需要操作数据库去匹配用户信息时,在 sql 语句中注入一些代码,去获取数据库信息或权限。接着,我们来看看在SQL注入代码,成功入侵数据库的案例。

    本案例代码来自上篇:初探Servlet,HTML + JDBC + MySQL的一个小案例

  • Statement :罪魁祸首

    Statement 是 Jdbc 提供的一个可以操作数据库的方法,通常用于增、删、改、查;用它的 statement.executeQuery(sql) 方法可以执行数据库语句。

    案例关键代码如下:

public class LoginServlet extends HttpServlet {

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		req.setCharacterEncoding("utf-8");
		Connection conn = null;
		Statement statement = null;
		ResultSet result = null;
		PrintWriter out = resp.getWriter();

		String user = req.getParameter("user");
		String pwd = req.getParameter("pwd");
		System.out.println("请求:user:" + user);
		System.out.println("请求:pwd:" + pwd);

		try {
			conn = JdbcUtils.getConnection();
			statement = conn.createStatement();
			String sql = "SELECT * FROM user WHERE(user_name='" + user + "' AND password='" + pwd + "')";
			System.out.println(sql);
			result = statement.executeQuery(sql);
		
			//result 返回 true 说明匹配成功
			if (result.next()) {
				out.println("user,login succeed");
			} else {
				out.println("login failed");
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JdbcUtils.release(statement, conn, result);
		}
	}
}

    如上代码中的 sql 语句: SELECT * FROM user WHERE(user_name='" + user + "' AND password='" + pwd + "'),看似没有任何问题,但配合着 Statement 就会出现漏洞了。接着我们来测试 SQL 注入。

  • 测试 SQL 注入攻击

    我的数据库有一张 user 表如下:

    我们使用表中的任何 user_name 和 password 都是可以登入的。接着,是用注入代码的方式: user 和 密码 都为

 1' OR '1'='1,也是可以登入成功的。

    sql 语句变成如上图控制台打印出来的那样,我们拿到数据库软件中跑一下,也是可以查出来。后面的 ‘1’ = ‘1’ 为真,这句 sql 语句其实变成了 SELECT * FROM user ,结果也就是查询出 user 所有数据信息。

  • 漏洞原因

    出现这种情况的原因是,每次在请求登入的时候时, Statement 在执行 sql 语句时都会把表单提交来的 用户名 和 密码 都重新拼接到 sql 语句中,然后在执行。这样会导致 sql 语句结构发生改变,攻击者利用这种方式攻击数据库就称为 SQL注入攻击。

  • PreparedStament 化解 SQL 注入

    PreparedStament 是 Statement 的子接口,它最大特点就是预处理 sql 语句。它在实例化时,必须传入 sql 语句,这与Statement 就有所区别。sql 语句中使用占位符 ? 来代替未知参数值,继而通过 preparedStatement.setXXX() 方法添加进 sql 语句中,这样就不会改变 sql 的结构,就能防止 SQL 注入攻击了。

代码修改为:

public class LoginServlet extends HttpServlet {

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		req.setCharacterEncoding("utf-8");
		Connection conn = null;
		PreparedStatement preparedStatement = null;
		ResultSet result = null;
		PrintWriter out = resp.getWriter();

		String user = req.getParameter("user");
		String pwd = req.getParameter("pwd");
		System.out.println("请求:user:" + user);
		System.out.println("请求:pwd:" + pwd);

		try {
			conn = JdbcUtils.getConnection();
			String sql = "SELECT * FROM user WHERE(user_name=? AND password=?)";
			preparedStatement = conn.prepareStatement(sql);
			System.out.println(sql);
			preparedStatement.setString(1, user);
			preparedStatement.setString(2, pwd);
			result = preparedStatement.executeQuery();
		
			//result 返回 true 说明匹配成功
			if (result.next()) {
				out.println("user,login succeed");
			} else {
				out.println("login failed");
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JdbcUtils.release(preparedStatement, conn, result);
		}
	}
}

    测试结果,使用 1' OR '1'='1 无法登入。

    所以,在日常开发中一般都使用 PreparedStatement ,它不仅可以防止 SQL 注入,同时能够预编译 sql  语句,效率更佳。

 

  • JDBC 增、删、改、查

    数据库为上文 user 表(图在上面)

  • JDBC 连接工具类
public class JdbcUtils {

	public static Connection getConnection() throws Exception {
		String driver = null;
		String jdbcUrl = null;
		String user = null;
		String password = null;

		InputStream inputStream = JdbcUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
		Properties properties = new Properties();
		properties.load(inputStream);
		driver = properties.getProperty("driver");
		jdbcUrl = properties.getProperty("jdbcUrl");
		user = properties.getProperty("user");
		password = properties.getProperty("password");
		Class.forName(driver);
		return (Connection) DriverManager.getConnection(jdbcUrl, user, password);
	}

	public static void release(Statement statement, Connection conn, ResultSet result) {
		try {
			if (statement != null) {
				statement.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		try {
			if (conn != null) {
				conn.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		try {
			if (result != null) {
				result.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
  • 增、删、改方法

public class JdbcTest {

	/**
	 * 提供: 增 、删、改3个功能的通用方法
	 * 
	 * @param sql 
	 * @param args sql 中占位符的值,可以用多个逗号隔开
	 */
	public void update(String sql, Object... args) throws Exception {
		Connection connection = null;
		PreparedStatement preparedStatement = null;

		try {
			connection = JdbcUtils.getConnection();
			System.out.println(sql);
			preparedStatement = connection.prepareStatement(sql);
			
			for (int i = 0; i < args.length; i++) {
				preparedStatement.setObject(i + 1, args[i]);
			}
			preparedStatement.executeUpdate();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JdbcUtils.release(preparedStatement, connection, null);
		}
	}

	@Test
	public void insert() throws Exception {
		String sql = "INSERT INTO user(user_name,sex,user_role,password,id_card,register_time)"
				+ " VALUES(?,?,?,?,?,?)";
		
		update(sql,"小红","女","VIP用户","xh","654...","2019-01-25 14:02:03");
	}

	@Test
	public void delete() throws Exception {
		// 删除 user 表中 user_name 为小红的信息
		String sql = "DELETE FROM user WHERE user_name=?";
		update(sql,"小红");
	}

	@Test
	public void modify() throws Exception {
		// user 表中 user_name 为小红 的用户,将其  user_role 改为 普通用户
		String sql = "UPDATE user set user_role=? WHERE user_name=?";
		update(sql,"普通用户","小红");
	}
}
  • 通用查询方法封装

    利用面向对象编程的思想,我们新建了一个 User 类,此类变量为 user 表对应的列名,代码如下:

package com.xww;

public class User {

	public String userName;
	public String password;
	public String registerTime;
	public String sex;
	public String userRole;
	public String idCard;

	public User(String userName, String password, String registerTime, 
			String sex, String userRole, String idCard) {
		super();
		this.userName = userName;
		this.sex = sex;
		this.userRole = userRole;
		this.password = password;
		this.idCard = idCard;
		this.registerTime = registerTime;
	}

	public String getUserName() {
		return userName;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}

	public String getSex() {
		return sex;
	}

	public void setSex(String sex) {
		this.sex = sex;
	}

	public String getUserRole() {
		return userRole;
	}

	public void setUserRole(String userRole) {
		this.userRole = userRole;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getIdCard() {
		return idCard;
	}

	public void setIdCard(String idCard) {
		this.idCard = idCard;
	}

	public String getRegisterTime() {
		return registerTime;
	}

	public void setRegisterTime(String registerTime) {
		this.registerTime = registerTime;
	}

	@Override
	public String toString() {
		return "User [userName=" + userName + ", password=" + password + ", registerTime=" + registerTime + ", sex="
				+ sex + ", userRole=" + userRole + ", idCard=" + idCard + "]";
	}
}

    那么查询 user 表的方法就可以写为这样(不够灵活):

	public User query(String sql) {
		Connection connection = null;
		PreparedStatement preparedStatement = null;
		ResultSet resultSet = null;
		User user = null;

		try {
			connection = JdbcUtils.getConnection();
			System.out.println(sql);
			preparedStatement = connection.prepareStatement(sql);
			resultSet = preparedStatement.executeQuery();

			while (resultSet.next()) {
				user = new User(resultSet.getString(2), resultSet.getString(3), resultSet.getString(4),
						resultSet.getString(5), resultSet.getString(6), resultSet.getString(7));
				System.out.println(user.toString());
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JdbcUtils.release(preparedStatement, connection, resultSet);
		}
		return user;
	}

     这样的确可以获取到 user 表,但是这个方法并不能通用。例如:我想查询 student 表时,student 表中字段名与 user 表肯定不一样,这样就会产生错误。所以,这种情况就可以利用 Java 反射来实现。可是,User 类中的变量相当于 user 表中的列名,如果让 User 类将变量名强制写成 user 表中一样,那就显得很局限,不能够通配。那么,就得用到 JDBC 提供的另一个接口了。

(1)ResultSetMetaData 接口

    它用于获取 ResultSet 结果集的元数据,用处是在于从查出的结果集中获取相应的列数、列名等。常用方法如下:

  • getColumnName(int column):获取指定列的名称
  • getColumnCount():返回当前 ResultSet 对象中的列数。
  • getColumnTypeName(int column):检索指定列的数据库特定的类型名称。
  • getColumnDisplaySize(int column):指示指定列的最大标准宽度,以字符为单位。
  • isNullable(int column):指示指定列中的值是否可以为 null。
  • isAutoIncrement(int column):指示是否自动为指定列进行编号,这样这些列仍然是只读的。

(2)利用 sql 语句添加别名的方式

    比如我要查询 user 表中 ‘小王’ 的详细信息,原本的 sql 语句:

    >  SELECT * FROM user WHERE user_name='小王'

要改为添加别名的方式,注意:别名一定是与User类中定义变量名一致,否则在反射时将找不到字段:

    >  SELECT user_name userName,password password,register_time registerTime,sex sex,user_role userRole,id_card idCard FROM user WHERE user_name='小王'

查出来的结果如下图:

    字段名都与 User 类中的变量名相一致了。

(3)利用 Java 反射给 User 类中的每个属性变量赋予值 .

    那么,经过这几翻的处理,我们的 User 类终于可以匹配数据库了。这个虽说可以实现通用查询,映射到实体类中,但明显在 sql 语句中那么点缺乏简练。整个查询代码如下:

public class JdbcTest {

	/**
	 * 利用 Java 反射机制,写的一个通用查询方法
	 * 
	 * @param sql
	 */
	public <T> T query(Class<T> clazz, String sql, Object... args) {
		T entity = null;

		Connection connection = null;
		PreparedStatement preparedStatement = null;
		ResultSet resultSet = null;
		User user = null;

		try {
			connection = JdbcUtils.getConnection();
//			System.out.println(sql);
			preparedStatement = connection.prepareStatement(sql);
			if (args != null) {
				for (int i = 0; i < args.length; i++) {
					preparedStatement.setObject(i + 1, args[i]);
				}
			}

			resultSet = preparedStatement.executeQuery();
			ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
			Map<String, Object> values = new HashMap<String, Object>();

	
			if (resultSet.next()) {
				for (int i = 0; i < resultSetMetaData.getColumnCount(); i++) {
					String columnLable = resultSetMetaData.getColumnLabel(i + 1);
					Object columnValue = resultSet.getObject(i + 1);
					values.put(columnLable, columnValue);
				}
			}

			System.out.println("--->数据库:"+values);
			if (values.size() > 0) {
				entity = (T) clazz.newInstance();
				for (Entry<String, Object> entry : values.entrySet()) {
					String fieldName = entry.getKey();
					Object fieldValue = entry.getValue();
					Field field = clazz.getDeclaredField(fieldName);
					field.setAccessible(true);
					field.set(entity, fieldValue);
				}
			}

		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JdbcUtils.release(preparedStatement, connection, resultSet);
		}
		return entity;
	}

	@Test
	public void qurey() {
		String sql = "SELECT user_name userName,password password,register_time registerTime,"
				+ "sex sex,user_role userRole,id_card idCard FROM user WHERE user_name=?";
		User user = query(User.class, sql, "小王");
		System.out.println("--->反射到 User 类中:"+user.toString());
	}
}

(4)BeanUtils 工具包

    BeanUtils 工具包是 Apache 出的一个辅助工具,主要方便对于 JavaBean 进行一系列的操作。例如:

  1. 可以对 JavaBean 的 getter()/setter() 方法进行赋值操作
  2. 可以将Map集合对象直接转为 JavaBean 对象,需要属性名一致
  3. 可以实现两个 JavaBean 对象的拷贝操作

    BeanUtils 工具类的常用方法如下:

  • BeanUtils.setProperty(bean, name, value);实现对象的赋值操作
  • ConvertUtils.register(Converter converter , ..);当需要将String数据转换成引用数据类型(自定义数据类型时),需要使用此方法实现转换
  • BeanUtils.populate(bean,Map);实现将Map拷贝到对象中
  • BeanUtils.copyProperties(newObject,oldObject);实现对象之间的拷贝

    现在我们导入 beanutils.jar 包和一个依赖的 loggin.jar 包:

    修改上面部分代码:

将                Field field = clazz.getDeclaredField(fieldName);
                    field.setAccessible(true);
                    field.set(entity, fieldValue);

修改为         BeanUtils.setProperty(entity, fieldName, fieldValue);

    运行代码,匹配数据库成功,并反射到 User 类中:

 

©原文链接:https://blog.csdn.net/smile_Running/article/details/86647778

@作者博客:_Xu2WeI

@更多博文:查看作者的更多博文

posted @ 2019-01-25 16:52  爱写Bug的程序猿  阅读(995)  评论(0编辑  收藏  举报