PHP项目怎么实现密码策略?

wen java案例 4

本文目录导读:

PHP项目怎么实现密码策略?

  1. 核心基础:安全存储(最重要)
  2. 密码复杂度策略
  3. 密码生命周期管理
  4. 防暴力破解(限流与锁定)
  5. 完整的注册-登录流程示例(简化版)
  6. 额外建议

在PHP项目中实现密码策略,通常需要从存储安全复杂度要求生命周期管理防暴力破解四个维度来设计。

以下是详细的实现方案和代码示例:

核心基础:安全存储(最重要)

无论策略多么复杂,密码存储方式不正确,一切归零。绝对不要使用MD5或SHA1

正确做法:使用 password_hash()password_verify()

这两个函数自PHP 5.5起内置,底层自动使用bcrypt算法(可配置),并自动处理加盐(Salting)和成本因子(Cost Factor)。

<?php
// 1. 注册时:哈希密码
$password = $_POST['password'];
// PASSWORD_DEFAULT 目前是 bcrypt,未来可能会更新为更强的算法
$hash = password_hash($password, PASSWORD_DEFAULT, ['cost' => 12]); // cost=12 保证运算足够慢
// 存入数据库,字段建议用 VARCHAR(255) 或以上
// INSERT INTO users (username, password_hash) VALUES (?, ?);
?>
<?php
// 2. 登录时:验证密码
$inputPassword = $_POST['password'];
$storedHash = $user['password_hash']; // 从数据库取出
if (password_verify($inputPassword, $storedHash)) {
    // 密码正确
    // 可选:检查是否需要重新哈希(如果后续提升了 cost)
    if (password_needs_rehash($storedHash, PASSWORD_DEFAULT, ['cost' => 12])) {
        $newHash = password_hash($inputPassword, PASSWORD_DEFAULT, ['cost' => 12]);
        // 更新数据库中的哈希值
        // UPDATE users SET password_hash = ? WHERE id = ?
    }
    echo "登录成功";
} else {
    echo "密码错误";
}

密码复杂度策略

这是用户注册时对密码内容的校验。

策略示例(常见规则):

  • 最小长度:8位或12位
  • 必须包含:大写字母、小写字母、数字、特殊字符(如 )
  • 禁止包含:用户名、邮箱前缀、常见弱密码(如 123456password
<?php
function validatePassword(string $password, string $username = '', string $email = ''): array {
    $errors = [];
    // 1. 长度检查
    if (strlen($password) < 8) {
        $errors[] = '密码长度至少8位';
    }
    if (strlen($password) > 128) {
        $errors[] = '密码长度不能超过128位';
    }
    // 2. 字符类型检查
    if (!preg_match('/[A-Z]/', $password)) {
        $errors[] = '必须包含大写字母';
    }
    if (!preg_match('/[a-z]/', $password)) {
        $errors[] = '必须包含小写字母';
    }
    if (!preg_match('/[0-9]/', $password)) {
        $errors[] = '必须包含数字';
    }
    if (!preg_match('/[!@#$%^&*()_+\-=\[\]{};:"\\|,.<>\/?]/', $password)) {
        $errors[] = '必须包含特殊字符';
    }
    // 3. 禁止包含个人信息(忽略大小写)
    if ($username && stripos($password, $username) !== false) {
        $errors[] = '密码不能包含用户名';
    }
    if ($email) {
        $localPart = explode('@', $email)[0];
        if ($localPart && stripos($password, $localPart) !== false) {
            $errors[] = '密码不能包含邮箱前缀';
        }
    }
    // 4. 防止常见弱密码(可以维护一个黑名单列表或文件)
    $commonPasswords = ['123456', 'password', '12345678', 'qwerty', 'abc123'];
    if (in_array(strtolower($password), $commonPasswords)) {
        $errors[] = '密码过于常见,请设置更复杂的密码';
    }
    return $errors;
}
// 使用
$errors = validatePassword($_POST['password'], $_POST['username'], $_POST['email']);
if (!empty($errors)) {
    foreach ($errors as $error) {
        echo '<div class="error">' . htmlspecialchars($error) . '</div>';
    }
    // 终止注册流程
}

密码生命周期管理

密码有效期

强制用户定期更换密码(如每90天)。

// 用户表增加字段:password_changed_at datetime
// 登录时检查
$daysSinceChange = (time() - strtotime($user['password_changed_at'])) / 86400;
if ($daysSinceChange > 90) {
    // 强制用户进入密码修改页面
    header('Location: /change-password?expired=1');
    exit;
}

密码历史记录

禁止用户使用最近N次(如5次)使用过的密码。

// 创建表 password_history (user_id, password_hash, created_at)
// 修改密码时:
// 1. 检查新密码是否匹配历史记录中的任何一个
$stmt = $pdo->prepare("SELECT password_hash FROM password_history WHERE user_id = ? ORDER BY created_at DESC LIMIT 5");
$stmt->execute([$userId]);
foreach ($stmt as $row) {
    if (password_verify($newPassword, $row['password_hash'])) {
        $errors[] = '不能使用最近使用过的密码';
        break;
    }
}
// 2. 通过后,将新密码哈希存入历史表,并删除最旧的一条(保持最多5条)
$stmt = $pdo->prepare("INSERT INTO password_history (user_id, password_hash, created_at) VALUES (?, ?, NOW())");
$stmt->execute([$userId, $newHash]);
$stmt = $pdo->prepare("DELETE FROM password_history WHERE user_id = ? AND id NOT IN (SELECT id FROM (SELECT id FROM password_history WHERE user_id = ? ORDER BY created_at DESC LIMIT 5) AS tmp)");
$stmt->execute([$userId, $userId]);

防暴力破解(限流与锁定)

密码策略再好,如果攻击者可以无限尝试,依然不安全。

策略A:登录失败次数限制 + 临时锁定

// 使用 Redis 或数据库缓存
// 用户表或登录日志表记录失败次数和最后尝试时间
$loginAttemptsKey = "login_attempts:{$username}"; // 如果有 Redis
// 登录验证前检查
$attempts = $redis ? $redis->get($loginAttemptsKey) : 0;
$lockoutTime = 15 * 60; // 15分钟锁定
if ($attempts >= 5) {
    $lastAttempt = $redis ? $redis->ttl($loginAttemptsKey) : 0;
    if ($lastAttempt > 0) {
        echo "账户已暂时锁定,请" . ceil($lastAttempt/60) . "分钟后重试";
        exit;
    }
}
// 验证密码
if (password_verify($password, $storedHash)) {
    // 成功:清除失败记录
    if ($redis) $redis->del($loginAttemptsKey);
    echo "登录成功";
} else {
    // 失败:增加计数,设置过期时间
    if ($redis) {
        $redis->incr($loginAttemptsKey);
        $redis->expire($loginAttemptsKey, $lockoutTime);
    } else {
        // 数据库实现:在 users 表加字段 login_attempts, locked_until
        // UPDATE users SET login_attempts = login_attempts + 1 WHERE username = ?
    }
    echo "用户名或密码错误";
}

策略B:表单添加验证码

失败N次后,显示验证码(如 Google reCAPTCHA 或 hCaptcha)。

// 在登录页面:
if ($attempts > 2) {
    // 显示 reCAPTCHA 组件
    echo '<div class="g-recaptcha" data-sitekey="你的站点密钥"></div>';
}
// 后端验证
if ($attempts > 2) {
    $recaptchaResponse = $_POST['g-recaptcha-response'];
    $verify = file_get_contents("https://www.google.com/recaptcha/api/siteverify?secret=你的密钥&response={$recaptchaResponse}");
    $data = json_decode($verify);
    if (!$data->success) {
        echo '请通过验证码验证';
        exit;
    }
}

完整的注册-登录流程示例(简化版)

<?php
// register.php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $username = $_POST['username'];
    $email = $_POST['email'];
    $password = $_POST['password'];
    $confirmPassword = $_POST['confirm_password'];
    $errors = [];
    // 1. 确认密码匹配
    if ($password !== $confirmPassword) {
        $errors[] = '两次输入的密码不一致';
    }
    // 2. 执行复杂度验证
    $passwordErrors = validatePassword($password, $username, $email);
    $errors = array_merge($errors, $passwordErrors);
    if (empty($errors)) {
        // 3. 生成安全的密码哈希
        $hash = password_hash($password, PASSWORD_DEFAULT, ['cost' => 12]);
        // 4. 存入数据库(并记录密码修改时间)
        $stmt = $pdo->prepare("INSERT INTO users (username, email, password_hash, password_changed_at) VALUES (?, ?, ?, NOW())");
        $stmt->execute([$username, $email, $hash]);
        echo "注册成功!";
    } else {
        // 显示错误列表
        foreach ($errors as $error) {
            echo "<p style='color:red'>" . htmlspecialchars($error) . "</p>";
        }
    }
}
?>

额外建议

  1. 双因素认证(2FA):对于高安全要求项目,增加 TOTP(基于时间的一次性密码)或短信验证码。
  2. 传输加密:使用 HTTPS,防止密码在传输过程中被截获。
  3. 前端辅助提示:前端可以使用 zxcvbn(Dropbox 开源的密码强度估算库)实时显示密码强度评分,但前端验证不能替代后端验证
  4. 日志审计:记录所有登录(成功/失败)、密码修改、账户锁定事件,便于事后追溯。

通过以上组合,你可以在PHP项目中构建一个符合行业最佳实践的密码安全体系。

抱歉,评论功能暂时关闭!