在JDBC中使用PreparedStatement代替Statement,同时预防SQL注入
2016-03-13 17:08
489 查看
本篇讲诉为何在JDBC操作数据库的过程中,要使用PreparedStatement对象来代替Statement对象。
在前面的JDBC学习中,对于Statement对象,我们已经知道是封装SQL语句并发送给数据库执行的功能,但是实际开发中,这个功能我们更经常用的是Statement类的子类PreparedStatement类的对象来实现,而不是采用Statement对象。
Statement和PreparedStatement的关系与区别在于:
① PreparedStatement类是Statement类的子类,拥有更多强大的功能。
② PreparedStatement类可以防止SQL注入攻击的问题,后面会说到。
③ PreparedStatement会对SQL语句进行预编译,以减轻数据库服务器的压力,而Statement则无法做到。
例1 :使用PreparedStatement对代码中Statement进行更换
构建一张user表,接着我们要在程序中使用JDBC对数据库进行User对象数据的添加,先用Statement对象来展示,后使用PreparedStatement对象,以此来比较两者的不同。
在MySQL数据库中新建jdbcdemo库,并构建user表:
创建工程,在工程中导入数据库连接驱动的jar包。在【src】目录下新建一个database.properties文件,内容如下:
构建JDBC的工具类,包括注册驱动,获取连接,释放资源和连接等,这部分同《JDBC操作数据库的学习(2)》中相同,代码如下:
View Code
通过上述代码,经过测试,根据页面带过来的表单数据name值为' or 1=1 or name='是可以进行用户登录的。
分析:我们若将表单填写的数据带到代码中的SQL语句,就形成如下的SQL命令:
select * from user where name='' or 1=1 or name=''
可以看到使用Statement对象就是将两个字符串拼接形成的SQL语句,这样做很可能会将判断条件改变,如上面的命令,在where语句中出现了or 1=1 这样一定会返回true的语句,就如同程序发送一条“select * from user where true”的句子,那么数据库执行这条语句根本不需要筛选条件,只要数据库有任意用户,都可以告诉程序你找到了该指定用户,那么我们连密码都不用填的只需要恶意SQL语句即可登录网站。这就是一个SQL注入的典型例子。
而使用PreparedStatement则不会,因为PreparedStatement的预编译,会将表单中所填写的数据进行编译,这种编译是包含字符过滤的编译,就好像对html进行过滤转义一样,这字符过滤最关键的因素在于PreparedStatement使用的是占位符,而不会像Statement那样因为拼接字符串而引入了引号,可以看到在PreparedStatement中即使接收的表单数据中SQL语句以引号包围,由于程序中的SQL语句使用占位符,因此就相当于条件为where name=' or 1=1 or name=',显然数据库并没有这样的记录,因此防止了SQL注入的问题。
关于SQL注入也算是一门有趣的学问,建议平时对这方面多学习些,也有利于我们更好地加强自己开发中的安全性。
在前面的JDBC学习中,对于Statement对象,我们已经知道是封装SQL语句并发送给数据库执行的功能,但是实际开发中,这个功能我们更经常用的是Statement类的子类PreparedStatement类的对象来实现,而不是采用Statement对象。
Statement和PreparedStatement的关系与区别在于:
① PreparedStatement类是Statement类的子类,拥有更多强大的功能。
② PreparedStatement类可以防止SQL注入攻击的问题,后面会说到。
③ PreparedStatement会对SQL语句进行预编译,以减轻数据库服务器的压力,而Statement则无法做到。
例1 :使用PreparedStatement对代码中Statement进行更换
构建一张user表,接着我们要在程序中使用JDBC对数据库进行User对象数据的添加,先用Statement对象来展示,后使用PreparedStatement对象,以此来比较两者的不同。
在MySQL数据库中新建jdbcdemo库,并构建user表:
1:创建一个数据库 create database jdbcdemo; 2:使用刚创建的数据库 use jdbcdemo; 3:创建一个user表 create table user( id int primary key, name varchar(40), age int );
创建工程,在工程中导入数据库连接驱动的jar包。在【src】目录下新建一个database.properties文件,内容如下:
driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/jdbcdemo username=root password=root
构建JDBC的工具类,包括注册驱动,获取连接,释放资源和连接等,这部分同《JDBC操作数据库的学习(2)》中相同,代码如下:
public class UserDaoImpl { public User find(String name) throws SQLException { Connection conn = null; Statement st = null; ResultSet rs = null; try{ conn = JdbcUtils.getConnection(); String sql = "select * from user where name='" +name+ "'"; st = conn.createStatement(); rs = st.executeQuery(sql); if(rs.next()) { User user = new User(); user.setId(rs.getInt("id")); user.setName(rs.getString("name")); user.setAge(rs.getInt("age")); return user; }else{ return null; } }finally{ JdbcUtils.release(conn, st, rs); } } }
View Code
通过上述代码,经过测试,根据页面带过来的表单数据name值为' or 1=1 or name='是可以进行用户登录的。
分析:我们若将表单填写的数据带到代码中的SQL语句,就形成如下的SQL命令:
select * from user where name='' or 1=1 or name=''
可以看到使用Statement对象就是将两个字符串拼接形成的SQL语句,这样做很可能会将判断条件改变,如上面的命令,在where语句中出现了or 1=1 这样一定会返回true的语句,就如同程序发送一条“select * from user where true”的句子,那么数据库执行这条语句根本不需要筛选条件,只要数据库有任意用户,都可以告诉程序你找到了该指定用户,那么我们连密码都不用填的只需要恶意SQL语句即可登录网站。这就是一个SQL注入的典型例子。
而使用PreparedStatement则不会,因为PreparedStatement的预编译,会将表单中所填写的数据进行编译,这种编译是包含字符过滤的编译,就好像对html进行过滤转义一样,这字符过滤最关键的因素在于PreparedStatement使用的是占位符,而不会像Statement那样因为拼接字符串而引入了引号,可以看到在PreparedStatement中即使接收的表单数据中SQL语句以引号包围,由于程序中的SQL语句使用占位符,因此就相当于条件为where name=' or 1=1 or name=',显然数据库并没有这样的记录,因此防止了SQL注入的问题。
关于SQL注入也算是一门有趣的学问,建议平时对这方面多学习些,也有利于我们更好地加强自己开发中的安全性。
相关文章推荐
- MySQL索引
- MySql连接——内连接、外连接(左连接、右连接、全连接)
- JDBC操作数据库的学习(2)
- SQL Server 2016 RC0 发布
- SQL Server 2016 RC0 发布
- JDBC操作数据库的学习(1)
- JDBC事务处理
- Oracle RAC启动CRS-1028,CRS-0223错误
- sql第二天
- Oracle RAC备份与恢复
- 查看mysql是否安装成功和mysql的版本信息
- MSSQLServer知识点总结:DDL(create,alter,drop,declare)-未完整
- 修改mysql root密码
- sqlite数据库改动及升级
- Mysql索引
- SQL Server将数据库设置为单用户模式
- SQL Server 检测到基于一致性的逻辑 I/O 错误 pageid 不正确(应为 1:1772,但实际为 0:0)。在文件 'D:\Program Files\Microsoft SQL Ser
- mysql 日期格式化
- MySQL性能优化的最佳20+条经验
- 尝试在数据库5 中提取逻辑页() 失败。该逻辑页属于分配单元xxx而非xxx