本文目录导读:

在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位
- 必须包含:大写字母、小写字母、数字、特殊字符(如 )
- 禁止包含:用户名、邮箱前缀、常见弱密码(如
123456、password)
<?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>";
}
}
}
?>
额外建议
- 双因素认证(2FA):对于高安全要求项目,增加 TOTP(基于时间的一次性密码)或短信验证码。
- 传输加密:使用 HTTPS,防止密码在传输过程中被截获。
- 前端辅助提示:前端可以使用
zxcvbn(Dropbox 开源的密码强度估算库)实时显示密码强度评分,但前端验证不能替代后端验证。 - 日志审计:记录所有登录(成功/失败)、密码修改、账户锁定事件,便于事后追溯。
通过以上组合,你可以在PHP项目中构建一个符合行业最佳实践的密码安全体系。