??JDBC 数据库连接(Connectiond对象)使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection 加载到内存中,再验证用户名和密码得花费 0.05s~1s 的时间,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。
??数据库连接对象不能够重复利用.若同时有几百人频繁的进行数据库连接操作将占用很多的系统资源,对于每一次数据库连接,使用完后都得断开,不能控制被创建的连接对象数,系统资源会被无限制的分配出去,可导致内存泄漏,服务器崩溃
??此时可以通过预先创建好Connection 对象,存起来重复使用,存了 Connection 的容器就是连接池
① 基本属性:连接池中连接对象所依赖的四要素
??driverClassName , url , username , password
② 其他属性:限制连接对象的配置
??初始化连接数:5 在连接池中事先准备好5个Connection对象
??最多连接数:10 在连接池中最多有10个Connection对象,其他客户端进入等待状态
??最少连接数 : 3 在连接池中最少存在3个Connection对象
??最长等待时间:5 min 使用5分钟来申请获取 Connection 对象,如果时间到还没有申请到,则提示,自动放弃
??最长超时时间:10min 在10分钟之内没有任何动作,即是自动放弃 Connection 对象。连接池使用 javax.sql.DataSource 接口来表示连接池. DataSource(数据源)和连接池(Connection Pool)是同一个
① 获取 Connection 对象
??连接对象: Connection conn=DriverManager.getConnection(url,username,password);
??连接池:Connection conn = DataSource对象.getConnection();
??获取Connection对象后二者操作是一样的
② 释放 Connection对象(Connection对象.close()):
??连接对象: 与数据库服务器断开
??连接池: 将Connection对象返还给连接池中,数据库服务器没有断开连接
??druid:是阿里巴巴研发出来的号称 Java语言领域性能最高的连接池
??安装包下载地址
// 演示基本代码
@Test
public void testDruidDataSource() throws Exception {
// 创建一个连接池对象
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/jdbcdemo");
ds.setUsername("root");
ds.setPassword("admin");
ds.setInitialSize(5);//初始化创建连接的个数
Connection conn = ds.getConnection();
System.out.println(conn.getClass());
}
创建连接池对象时,可将连接数据库信息编写到 Properties 配置文件中,然后再读取到内存中来使用
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///jdbcdemo
jdbc.user=root
jdbc.password=root
工具类
public class DruidUtil {
private static DataSource dataSource=null;
static {
try {
Properties properties=new Properties();
properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("druid.properties"));
dataSource = DruidDataSourceFactory.createDataSource(properties);//通过此语句进行赋值操作
} catch (Exception e) {
e.printStackTrace();
}
}
/*获取连接对象:通过数据源对象创建连接对象*/
public static Connection getConnection(){
try {
return dataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
}
测试类
public class DruidTest {
public static void main(String[] args) throws SQLException {
Connection connection = DruidUtil.getConnection();
//创建连接对象
Statement statement = connection.createStatement();
String sql="SELECT * from t_employee";
ResultSet resultSet = statement.executeQuery(sql);
while (resultSet.next()){
System.out.println(resultSet.getString("ename"));
}
}
}
数据库表
CREATE TABLE `t_employee` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`eid` varchar(10) NOT NULL,
`ename` varchar(6) NOT NULL,
`job` varchar(10) DEFAULT NULL,
`salary` int(10) DEFAULT NULL,
`password` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `eid` (`eid`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8;
实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {
private Long id;
private String eid;
private String ename;
private String password;
private String job;
private Integer salary;
}
??SQL注入是恶意用户通过在表单中填写包含SQL关键字的数据来使数据库执行非常规代码的过程。QL数据库的操作是通过SQL语句来执行的,而无论是执行代码还是数据项都必须写在SQL语句中,若在数据项中加入了某些SQL语句关键字(如SELECT、DROP等),这些关键字有可能在数据库写入或读取数据时得到执行。
① 可显注入
??攻击者可以直接在当前界面内容中获取想要获得的内容。
② 报错注入:
??数据库查询返回结果并没有在页面中显示,但是应用程序将数据库报错信息打印到了页面中,所以攻击者可以构造数据库报错语句,从报错信息中获取想要获得的内容。
③ 盲注:
??数据库查询结果无法从直观页面中获取,攻击者通过使用数据库逻辑或使数据库库执行延时等方法获取想要获得的内容。
④ 时间盲注:
??常用函数 sleep ()
??分割函数 substr、substring、left
??分割函数编码后可不用引号,ascii () hex ()
??一般时间盲注我们还需要使用条件判断函数
??if(expre1,expre2,expre3)
??当 expre1 为 true 时,返回 expre2,false 时,返回 expre3
⑤ 布尔盲注::
??这种注入会出现在 注册、ip 头、留言板等需要写入数据的地方,如用 sqlmap 会产生大量垃圾数据。尝试性插入、引号、双引号、转义符 \ 让语句不能正常执行,若插入失败,更新失败,再深入测试确定是否存在注入。
⑥ 二次注入:
??sql 语句的变量并不是直接传入的变量,而是通过其它的方式保存到了数据库,形成二次注入。没有单引号的 sql 语句中,进行 16 进制编码,这样就不会带有单引号。
⑦ 宽字节注入:
??一个字符的大小是一个字节的,称为窄字节;如果一个字符的大小是两个字节的,称为宽字节
??字符注入时需要逃逸单引号,但 php 提供了魔术引号开关 magic_quotes_gpc 和 addslashes (),iconv () 函数作为防御,特点是自动给传入的参数如单引号,双引号,反斜杠,%00 前面加一个反斜杠,进行转义,避免单引号进行逃逸。
??单引号转义为 ‘ , mysql 会将 \ 编码为 %5c ,宽字节中将 % df 加上 %5c 就变成了一个汉字 “运”,如此绕过转义。若数据库是 GBK 格式而非默认的 UTF-8 格式,可利用两个 url 编码是一个汉字的特点,组合一个汉字从而解决”\“问题,完成单引号的逃逸。
⑧ 数字型注入::
??访问页面时经常会出现 http:xxx.admin.com?id=1 这种形式的 url,这个时候我们输入 and 1=1 与 and 1=2, 进行判定看是否会出现 and 1=1 回显正常,and 1=2 回显不正常的情况。如果出现了我们一般就认为这里存在注入,并且是数字型注入。如果没有出现那么并不能排查这里没有注入,只是排除这里不存在数字型的注入。
⑨ 字符型注入:
??url 后输入’ and 1=1 and ‘1’=’1,’ and 1=2 and ‘1’=’1。看是否会出现报错的现象。如果存在我们可以进一步的闭合我们的语句。
⑩ http 头部注入:
??user-agent: 判定用户使用的操作系统,以及使用的浏览器的版本。
??cookie: 判定用户的身份,进行 session 跟踪可以存储在用户本地终端上的数据,简单理解为用户的一张身份辨别卡。
??x-forwarded-for: 是用来识别通过 HTTP 代理或负载均衡方式连接到 Web 服务器的客户端最原始的 IP 地址的 HTTP 请求头字段。
??client-ip: 数据库保存客户端 IP 的参数。
??rerferer: 浏览器向 web 端表明自己从哪个连接而来。
??host: 客户端指定自己想访问的 WEB 服务器的域名 / IP 地址和端口号。
① 相同点:
??都是sql执行对象,可以实现增删改查
??都是接口,Statement是PreparedStatement父接口
② 区别:
??创建对象的语法不同
??执行CRUD的方法不同
??Statement是执行sql时编译,多次执行就编译多次;PreparedStatement可以编译一次,多次反复执行
??PreparedStatement可以防止sql注入
??PreparedStatement采用预编译机制,性能更高
??使用 Statement 语句对象,会将参数直接拼接到 SQL 中执行,此时参数若改变了SQL的语法结构,执行结果就会出问题,如登录查询的 SQL 中,用户名参数值为: ’ or 1=1 or’, 此时,填写的密码无论是什么,登录都会成功, 这就是 SQL 注入的问题.
??此问题可通过预编译语句对象,预先发送带有占位符的 SQL 到数据库中进行编译, 语句结构固定下来,设置参数给对应的占位符,然后再执行 SQL,这样无论是什么参数,都不会再改变 SQL 的语法结构,达到防止 SQL 注入问题的目的
public class UserTest {
/*使用账号密码执行登录:sql注入现象演示*/
@Test
public void test_statement() throws Exception{
Connection connection = DruidUtil.getConnection();
Statement statement = connection.createStatement();
// 注释正确的帐号,通过sql注入可使用错误的帐号登录
// String username="大黄";
String ename="fgdr' or 1=1 or '";
String password="123";
String sql="SELECT * from t_employee WHERE ename='"+ename+"' and password='"+password+"'";
System.out.println(sql);
//传入的参数参与了sql编译过程所有造成sql注入现象
ResultSet resultSet = statement.executeQuery(sql);
if(resultSet.next()){
System.out.println("登录成功");
}else{
System.out.println("登录失败");
}
}
/*使用账号密码执行登录:sql注入解决
* 客户端传入的参数不能参与sql编译的过程,从而解决了注入的问题
*
* */
@Test
public void test_preparedStatement() throws Exception{
String username="qwewqe' or 1=1 or '";
// String username="admin";
String password="123";
Connection connection = DruidUtil.getConnection();
String sql="SELECT * from t_employee WHERE ename=? and password=?";
PreparedStatement statement = connection.prepareStatement(sql);
statement.setObject(1,username);
statement.setObject(2,password);
System.out.println(sql);
ResultSet resultSet = statement.executeQuery();
if(resultSet.next()){
System.out.println("登录成功");
}else{
System.out.println("登录失败");
}
}
}