本文目录导读:

在PHP项目中,预编译语句(Prepared Statements)是防止SQL注入最有效的方法之一,它通过将SQL语句结构和数据分离来实现安全性,下面是详细的实现方式:
使用PDO(PHP Data Objects)
基本示例
<?php
// 数据库连接
$dsn = 'mysql:host=localhost;dbname=testdb;charset=utf8mb4';
$username = 'root';
$password = '';
try {
$pdo = new PDO($dsn, $username, $password, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false, // 关闭模拟预编译
]);
} catch (PDOException $e) {
die('连接失败: ' . $e->getMessage());
}
// 使用预编译语句
$sql = "SELECT * FROM users WHERE username = :username AND password = :password";
$stmt = $pdo->prepare($sql);
// 绑定参数
$stmt->bindParam(':username', $username);
$stmt->bindParam(':password', $password);
// 设置参数值(这些值来自用户输入)
$username = $_POST['username'];
$password = $_POST['password'];
// 执行查询
$stmt->execute();
$result = $stmt->fetchAll();
?>
使用位置占位符(?)
<?php $sql = "SELECT * FROM users WHERE username = ? AND password = ?"; $stmt = $pdo->prepare($sql); // 绑定参数(位置顺序) $stmt->bindParam(1, $username); $stmt->bindParam(2, $password); // 或者使用数组方式 $stmt->execute([$username, $password]); ?>
使用MySQLi
面向对象方式
<?php
// 数据库连接
$mysqli = new mysqli('localhost', 'root', '', 'testdb');
if ($mysqli->connect_error) {
die('连接失败: ' . $mysqli->connect_error);
}
// 设置字符集
$mysqli->set_charset('utf8mb4');
// 预编译语句
$sql = "SELECT * FROM users WHERE username = ? AND password = ?";
$stmt = $mysqli->prepare($sql);
// 绑定参数(i=整数, d=浮点数, s=字符串, b=二进制)
$stmt->bind_param("ss", $username, $password);
// 设置参数
$username = $_POST['username'];
$password = $_POST['password'];
// 执行
$stmt->execute();
// 获取结果
$result = $stmt->get_result();
$users = $result->fetch_all(MYSQLI_ASSOC);
// 关闭语句
$stmt->close();
$mysqli->close();
?>
插入数据示例
<?php
// 插入数据(绝对安全)
$sql = "INSERT INTO users (username, email, age) VALUES (?, ?, ?)";
$stmt = $mysqli->prepare($sql);
$stmt->bind_param("ssi", $username, $email, $age);
$username = $_POST['username'];
$email = $_POST['email'];
$age = (int)$_POST['age'];
$stmt->execute();
echo "新增记录ID: " . $stmt->insert_id;
?>
预编译语句如何防止注入
原理图解
用户输入: ' OR '1'='1
↓
传统SQL拼接: SELECT * FROM users WHERE username = '' OR '1'='1'
→ SQL注入成功!
预编译方式:
1. 发送SQL模板: SELECT * FROM users WHERE username = ?
2. 数据库解析SQL结构
3. 发送参数: "' OR '1'='1"
4. 数据库将参数当作字符串处理,不解析SQL语法
→ 安全!
最佳实践建议
配置优化
<?php
// PDO最佳配置
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // 异常模式
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, // 关联数组
PDO::ATTR_EMULATE_PREPARES => false, // 关闭模拟预编译
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4" // 设置字符集
];
$pdo = new PDO($dsn, $username, $password, $options);
?>
多种数据类型绑定
<?php
// 批量插入
$users = [
['Alice', 'alice@example.com', 25],
['Bob', 'bob@example.com', 30],
['Charlie', 'charlie@example.com', 28]
];
$sql = "INSERT INTO users (username, email, age) VALUES (?, ?, ?)";
$stmt = $pdo->prepare($sql);
$pdo->beginTransaction();
foreach ($users as $user) {
$stmt->execute($user);
}
$pdo->commit();
?>
查询结果处理
<?php
// 获取单条记录
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$userId]);
$user = $stmt->fetch();
// 获取多条记录
$stmt = $pdo->prepare("SELECT * FROM users WHERE age > ?");
$stmt->execute([$minAge]);
$users = $stmt->fetchAll();
// 获取影响行数
$stmt = $pdo->prepare("UPDATE users SET status = ? WHERE id = ?");
$stmt->execute(['active', $userId]);
echo "更新了 " . $stmt->rowCount() . " 行";
?>
常见误区
❌ 错误做法
<?php
// 错误:混合使用
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = $id"); // 仍然不安全
// 错误:不完全使用
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = " . $_GET['username']);
$stmt->execute(); // 仍然不安全
// 错误:忘记绑定参数
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");
$stmt->execute(); // 没有绑定参数会导致错误
?>
✅ 正确做法
<?php
// 始终使用占位符
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);
// 或者命名占位符
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");
$stmt->execute([':username' => $_POST['username']]);
?>
完整安全示例
<?php
class Database {
private $pdo;
public function __construct($config) {
try {
$this->pdo = new PDO(
"mysql:host={$config['host']};dbname={$config['dbname']};charset=utf8mb4",
$config['username'],
$config['password'],
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]
);
} catch (PDOException $e) {
throw new Exception("数据库连接失败");
}
}
public function query($sql, $params = []) {
try {
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
return $stmt;
} catch (PDOException $e) {
throw new Exception("查询执行失败");
}
}
public function fetch($sql, $params = []) {
return $this->query($sql, $params)->fetch();
}
public function fetchAll($sql, $params = []) {
return $this->query($sql, $params)->fetchAll();
}
public function insert($table, $data) {
$columns = implode(', ', array_keys($data));
$placeholders = implode(', ', array_fill(0, count($data), '?'));
$sql = "INSERT INTO {$table} ({$columns}) VALUES ({$placeholders})";
$this->query($sql, array_values($data));
return $this->pdo->lastInsertId();
}
}
// 使用示例
$db = new Database([
'host' => 'localhost',
'dbname' => 'test',
'username' => 'root',
'password' => ''
]);
// 安全查询
$user = $db->fetch("SELECT * FROM users WHERE id = ?", [$_GET['id']]);
$users = $db->fetchAll("SELECT * FROM users WHERE age > ?", [18]);
// 安全插入
$newId = $db->insert('users', [
'username' => $_POST['username'],
'email' => $_POST['email'],
'password' => password_hash($_POST['password'], PASSWORD_DEFAULT)
]);
?>
预编译语句防止SQL注入的核心机制:
- SQL结构预定义:数据库先编译SQL语句结构
- 数据后绑定:参数值在编译后单独发送
- 自动转义:数据库自动处理特殊字符
- 类型安全:严格区分SQL代码和数据
永远不要相信用户输入,始终使用预编译语句处理所有数据库操作!
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播