第 22 章 — 安全
第 22 章 — 安全:加密、CSRF、XSS、SQL 注入与输入验证
22.1 密码安全
<?php
// ✅ 正确:使用 password_hash
$hash = password_hash('mysecretpassword', PASSWORD_DEFAULT);
echo $hash;
// $2y$10$...(bcrypt 哈希)
// ✅ 正确:验证密码
if (password_verify('mysecretpassword', $hash)) {
echo '密码正确';
}
// 需要重新哈希(算法升级时)
if (password_needs_rehash($hash, PASSWORD_DEFAULT)) {
$newHash = password_hash('mysecretpassword', PASSWORD_DEFAULT);
// 更新数据库
}
// ❌ 错误:永远不要这样做
// md5($password)
// sha1($password)
// hash('sha256', $password)
22.2 XSS 防护
<?php
// 输出转义
echo htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');
// HTMLPurifier(富文本过滤)
// composer require ezyang/htmlpurifier
$config = HTMLPurifier_Config::createDefault();
$purifier = new HTMLPurifier($config);
$cleanHtml = $purifier->purify($dirtyHtml);
// CSP 头
header("Content-Security-Policy: default-src 'self'; script-src 'self'");
22.3 CSRF 防护
<?php
class CSRFProtection
{
public static function generateToken(): string
{
$token = bin2hex(random_bytes(32));
$_SESSION['csrf_token'] = $token;
return $token;
}
public static function validateToken(?string $token): bool
{
if ($token === null || !isset($_SESSION['csrf_token'])) {
return false;
}
return hash_equals($_SESSION['csrf_token'], $token);
}
public static function field(): string
{
$token = self::generateToken();
return '<input type="hidden" name="_token" value="' . htmlspecialchars($token) . '">';
}
}
22.4 SQL 注入防护
<?php
// ✅ 正确:使用预处理语句
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = ?');
$stmt->execute([$email]);
// ✅ 正确:命名参数
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute(['email' => $email]);
// ❌ 错误:字符串拼接
// $sql = "SELECT * FROM users WHERE email = '$email'";
22.5 输入验证
<?php
declare(strict_types=1);
class InputValidator
{
public static function validate(array $data, array $rules): array
{
$errors = [];
foreach ($rules as $field => $rule) {
$value = $data[$field] ?? null;
$ruleList = explode('|', $rule);
foreach ($ruleList as $r) {
match (true) {
$r === 'required' && ($value === null || $value === '') =>
$errors[$field][] = "{$field} 是必填字段",
str_starts_with($r, 'min:') => self::validateMin($field, $value, $r, $errors),
str_starts_with($r, 'max:') => self::validateMax($field, $value, $r, $errors),
$r === 'email' && $value !== null && !filter_var($value, FILTER_VALIDATE_EMAIL) =>
$errors[$field][] = "{$field} 必须是有效的邮箱",
$r === 'numeric' && $value !== null && !is_numeric($value) =>
$errors[$field][] = "{$field} 必须是数字",
$r === 'url' && $value !== null && !filter_var($value, FILTER_VALIDATE_URL) =>
$errors[$field][] = "{$field} 必须是有效的 URL",
default => null,
};
}
}
return $errors;
}
private static function validateMin(string $field, mixed $value, string $rule, array &$errors): void
{
$min = (int) substr($rule, 4);
if (is_string($value) && mb_strlen($value) < $min) {
$errors[$field][] = "{$field} 至少 {$min} 个字符";
}
}
private static function validateMax(string $field, mixed $value, string $rule, array &$errors): void
{
$max = (int) substr($rule, 4);
if (is_string($value) && mb_strlen($value) > $max) {
$errors[$field][] = "{$field} 最多 {$max} 个字符";
}
}
}
22.6 加密
<?php
// AES-256-GCM 加密
function encrypt(string $data, string $key): string
{
$iv = random_bytes(16);
$tag = '';
$encrypted = openssl_encrypt($data, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag);
return base64_encode($iv . $tag . $encrypted);
}
function decrypt(string $encoded, string $key): string
{
$decoded = base64_decode($encoded);
$iv = substr($decoded, 0, 16);
$tag = substr($decoded, 16, 16);
$encrypted = substr($decoded, 32);
return openssl_decrypt($encrypted, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag);
}
22.7 安全清单
| 攻击 | 防护措施 |
|---|---|
| SQL 注入 | 预处理语句 |
| XSS | htmlspecialchars(), CSP |
| CSRF | Token 验证 |
| 文件上传 | 类型检查、重命名、大小限制 |
| 目录遍历 | 白名单、basename() |
| 会话劫持 | HTTPS、HttpOnly、Secure、SameSite |
| 暴力破解 | 限流、验证码 |
| 敏感数据泄露 | 加密存储、环境变量 |
22.8 扩展阅读
上一章:第 21 章 — 日志 下一章:第 23 章 — 性能优化