在网站登录场景中,暴力破解是攻击者常用的攻击方式,通过大量尝试密码组合试图获取合法账号权限,PHP可以通过多种策略有效防范这类攻击。

暴力破解的常见危害
暴力破解攻击会消耗服务器大量资源,同时可能导致用户账号被盗用,敏感数据泄露,因此登录模块的安全防护是网站开发的重要环节。PHP作为后端语言,可以在登录逻辑层实现多层防护策略。
核心防护方法
1. 登录失败次数限制
记录同一账号的登录失败次数,当失败次数超过阈值时临时锁定账号,避免攻击者无限制尝试密码。可以使用文件、数据库或者缓存来存储失败记录。
以下是基于文件存储失败记录的示例代码:
<?php
// 定义失败次数阈值和锁定时间(单位:秒)
$max_fail_count = 5;
$lock_time = 300;
// 获取用户输入的账号和密码
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
// 失败记录文件路径
$fail_file = __DIR__ . '/fail_record/' . md5($username) . '.txt';
// 检查账号是否被锁定
if (file_exists($fail_file)) {
$content = file_get_contents($fail_file);
$record = json_decode($content, true);
// 如果当前时间在锁定时间内,拒绝登录
if (time() - $record['last_fail_time'] < $lock_time) {
die('账号已锁定,请' . ceil(($lock_time - (time() - $record['last_fail_time'])) / 60) . '分钟后再试');
} else {
// 超出锁定时间,清除记录
unlink($fail_file);
}
}
// 模拟验证账号密码(实际开发中替换为数据库查询验证)
$correct_password = 'user123456';
if ($username === 'test_user' && $password === $correct_password) {
// 登录成功,清除失败记录
if (file_exists($fail_file)) {
unlink($fail_file);
}
echo '登录成功';
} else {
// 登录失败,记录失败信息
if (!file_exists(dirname($fail_file))) {
mkdir(dirname($fail_file), 0755, true);
}
$fail_count = 1;
$last_fail_time = time();
if (file_exists($fail_file)) {
$old_record = json_decode(file_get_contents($fail_file), true);
$fail_count = $old_record['fail_count'] + 1;
$last_fail_time = time();
}
// 保存失败记录
file_put_contents($fail_file, json_encode([
'fail_count' => $fail_count,
'last_fail_time' => $last_fail_time
]));
// 判断是否达到锁定阈值
if ($fail_count >= $max_fail_count) {
die('连续登录失败超过' . $max_fail_count . '次,账号已锁定' . ($lock_time / 60) . '分钟');
} else {
echo '登录失败,还剩' . ($max_fail_count - $fail_count) . '次尝试机会';
}
}
?>
2. 时间窗口限制
除了按次数限制,还可以限制同一账号在特定时间窗口内的登录尝试次数,比如1分钟内最多尝试3次,进一步降低攻击效率。
实现思路是记录每次登录失败的时间,统计时间窗口内的失败次数,示例如下:
<?php
// 时间窗口配置(单位:秒)
$time_window = 60;
$max_attempts = 3;
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
// 时间窗口记录文件
$attempt_file = __DIR__ . '/attempt_record/' . md5($username) . '.txt';
// 获取当前时间窗口内的尝试记录
$attempts = [];
if (file_exists($attempt_file)) {
$content = file_get_contents($attempt_file);
$attempts = json_decode($content, true) ?: [];
// 过滤超出时间窗口的记录
$attempts = array_filter($attempts, function($time) use ($time_window) {
return time() - $time < $time_window;
});
}
// 检查是否超过尝试次数
if (count($attempts) >= $max_attempts) {
die('1分钟内尝试次数过多,请稍后再试');
}
// 模拟密码验证
$correct_password = 'user123456';
if ($username === 'test_user' && $password === $correct_password) {
// 登录成功清除记录
if (file_exists($attempt_file)) {
unlink($attempt_file);
}
echo '登录成功';
} else {
// 记录本次失败时间
$attempts[] = time();
if (!file_exists(dirname($attempt_file))) {
mkdir(dirname($attempt_file), 0755, true);
}
file_put_contents($attempt_file, json_encode($attempts));
echo '登录失败,当前时间窗口内还剩' . ($max_attempts - count($attempts)) . '次尝试机会';
}
?>
3. 验证码校验
在登录表单中加入验证码,每次登录都需要输入正确的验证码,能有效阻断自动化脚本的暴力破解尝试。可以结合PHP的GD库生成图形验证码,或者使用第三方验证码服务。
简单的图形验证码生成示例:
<?php
// 生成验证码
session_start();
$code = '';
for ($i = 0; $i < 4; $i++) {
$code .= rand(0, 9);
}
$_SESSION['captcha_code'] = $code;
// 创建画布
$width = 100;
$height = 30;
$image = imagecreatetruecolor($width, $height);
$bg_color = imagecolorallocate($image, 255, 255, 255);
$text_color = imagecolorallocate($image, 0, 0, 0);
imagefill($image, 0, 0, $bg_color);
// 写入验证码
imagestring($image, 5, 20, 8, $code, $text_color);
// 输出图片
header('Content-Type: image/png');
imagepng($image);
imagedestroy($image);
?>
登录时校验用户输入的验证码是否和session中存储的一致即可。
4. IP层面的限制
可以记录尝试登录的IP地址,如果同一IP短时间内发起大量登录请求,可以临时封禁该IP。可以结合数据库或者Redis存储IP的失败请求记录,实现封禁逻辑。
防护策略的优化建议
- 可以将失败记录、尝试记录存储在Redis等高性能缓存中,提升读写效率,适合高并发场景。
- 对于管理后台等高权限账号,可以额外增加二次验证机制,比如短信验证码、邮箱验证等。
- 定期清理过期的失败记录和尝试记录,避免无用文件或数据堆积占用存储空间。
- 密码存储时必须使用
password_hash()函数进行哈希处理,不要明文存储密码,即使被拖库也无法直接获取明文密码。
总结
PHP防止暴力破解密码需要结合多种策略,从账号维度、时间维度、IP维度以及验证码等多个层面共同防护,才能最大程度降低暴力破解的风险。开发者可以根据自身业务场景选择合适的方案,或者组合多种方案实现更完善的防护体系。