什么是SQL注入
SQL注入就是外部传入特殊的SQL查询参数,经mysql解析执行后得到非预期结果。如要查询昵称为张三的用户,如果接收到的查询参数是张三 and 1=1这时会查询到所有用户。
# 常规的查询语句结构
select * from user where username='张三'
# 被注入后的查询语句结构
select * from user where username='张三' and 1=1
服务器如何避免被SQL注入
目前都是通过SQL预编译避免SQL注入。
什么是SQL预编译?
通常服务器接收到一条SQL需要经过语法、语义解析,生成执行计划,最后执行返回结果。一般同一条SQL会执行多次,只是字段参数值不同,通过预编译后可以生成一条SQL模板,相同的SQL可以省略解析的步骤,直接替换字段参数值即可执行。可以提高执行效率。
为什么SQL预编译可以防止SQL注入?
经过预编译后,mysql在执行参数替换时只会把参数值当做普通的值加入到SQL中,而不会再将整个SQL重新编译。
如何使用预编译
在PHP中
// 连接数据库
$link = mysqli_connect("127.0.0.1", "root","root","db_xb", 3308);
if ($link) {
echo "连接成功...", PHP_EOL;
// 创建预编译的Statement
$stmt = mysqli_prepare($link, "select username,nickname from xb_sys_user where username=?");
$username = "uu";
// 绑定预编译SQL的参数
$stmt->bind_param("s", $username);
// 将查询结果绑定到指定的变量
$stmt->bind_result($db_username, $db_nickname);
// 执行预编译SQL
$stmt->execute();
// 获取执行后的结果
$stmt->fetch();
// 使用关联数组的方式获取结果
// $rs = $stmt->get_result();
// dump($rs->fetch_assoc());
printf("%s is in username: %s nickname: %s\n", $username, $db_username, $db_nickname);
mysqli_close($link);
}
在Java中
// 注册JDBC驱动
Class.forName("com.mysql.cj.jdbc.Driver");
Connection connection = DriverManager.getConnection(
"jdbc:mysql://localhost:3308/db_xb",
"root",
"root"
);
// 预编译方式
PreparedStatement preparedStatement = connection.prepareStatement("select * from xb_sys_user where username=?");
// 设置预编译SQL的参数
preparedStatement.setString(1, "uu");
// 执行预编译SQL
ResultSet rs = preparedStatement.executeQuery();
System.out.println("执行查询成功...");
// 获取结果
while (rs.next()) {
System.out.println(String.format(
"按字段索引位置取\tid: %d username: %s nickname: %s password: %s status: %d",
rs.getLong(1), // 按数据表中的字段索引位置开始取
rs.getString(2),
rs.getString(3),
rs.getString(4),
rs.getInt(5)
));
System.out.println(String.format(
"按字段名取\tid: %d username: %s nickname: %s password: %s status: %d",
rs.getLong("id"), // 按数据表中的字段名开始取
rs.getString("username"),
rs.getString("nickname"),
rs.getString("password"),
rs.getInt("status")
));
}