强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

PHP 完全指南 / 第 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 注入预处理语句
XSShtmlspecialchars(), CSP
CSRFToken 验证
文件上传类型检查、重命名、大小限制
目录遍历白名单、basename()
会话劫持HTTPS、HttpOnly、Secure、SameSite
暴力破解限流、验证码
敏感数据泄露加密存储、环境变量

22.8 扩展阅读


上一章第 21 章 — 日志 下一章第 23 章 — 性能优化