本文目录导读:

在PHP项目中实现账号锁定的自动解锁,通常有以下几种常见方案,我会按照从简单到复杂的顺序为你介绍:
基于时间的自动解锁(推荐)
核心思路
记录锁定时间 + 每次登录时检查时间是否过期。
实现步骤
数据库字段设计
ALTER TABLE `users` ADD COLUMN `login_attempts` int(11) DEFAULT 0 COMMENT '登录失败次数', ADD COLUMN `locked_until` datetime DEFAULT NULL COMMENT '锁定截止时间';
登录逻辑代码
<?php
// 登录检测
function authenticate($username, $password) {
$user = getUserByUsername($username);
// 检查是否被锁定
if ($user['locked_until'] !== null && strtotime($user['locked_until']) > time()) {
$remaining = strtotime($user['locked_until']) - time();
return [
'success' => false,
'message' => "账号已被锁定,请等待 {$remaining} 秒后重试"
];
}
// 验证密码
if (password_verify($password, $user['password_hash'])) {
// 登录成功:重置失败次数和锁定状态
resetLoginAttempts($user['id']);
return ['success' => true, 'message' => '登录成功'];
} else {
// 登录失败:增加失败次数
$attempts = incrementLoginAttempts($user['id']);
// 达到阈值则锁定(5次失败后锁定15分钟)
$maxAttempts = 5;
$lockDuration = 15 * 60; // 15分钟
if ($attempts >= $maxAttempts) {
lockAccount($user['id'], date('Y-m-d H:i:s', time() + $lockDuration));
return [
'success' => false,
'message' => "登录失败次数过多,账号已被锁定 {$lockDuration} 分钟"
];
}
$remaining = $maxAttempts - $attempts;
return [
'success' => false,
'message' => "密码错误,还有 {$remaining} 次尝试机会"
];
}
}
// 辅助函数
function resetLoginAttempts($userId) {
DB::update("UPDATE users SET login_attempts = 0, locked_until = NULL WHERE id = ?", [$userId]);
}
function incrementLoginAttempts($userId) {
DB::query("UPDATE users SET login_attempts = login_attempts + 1 WHERE id = ?", [$userId]);
return DB::getOne("SELECT login_attempts FROM users WHERE id = ?", [$userId]);
}
function lockAccount($userId, $lockedUntil) {
DB::query("UPDATE users SET locked_until = ? WHERE id = ?", [$lockedUntil, $userId]);
}
?>
缓存自动解锁(Laravel示例)
使用Redis或Memcached存储锁定信息,自动过期。
Laravel实现
<?php
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Hash;
class LoginController
{
const MAX_ATTEMPTS = 5;
const LOCKOUT_DURATION = 15; // 分钟
public function login(Request $request)
{
$key = 'login_attempts_' . $request->username;
// 检查是否锁定
if (Cache::has($key . '_locked')) {
$remaining = Cache::ttl($key . '_locked');
return back()->withErrors([
'username' => "账号已被锁定,请等待 {$remaining} 秒后重试"
]);
}
// 验证凭据
if (Auth::attempt($request->only('username', 'password'))) {
// 清除锁定记录
Cache::forget($key);
Cache::forget($key . '_locked');
return redirect()->intended('/home');
}
// 记录失败次数
$attempts = Cache::increment($key, 1);
if ($attempts >= self::MAX_ATTEMPTS) {
Cache::put($key . '_locked', true, self::LOCKOUT_DURATION * 60);
Cache::forget($key); // 重置计数
return back()->withErrors([
'username' => "登录失败次数过多,账号已被锁定 " . self::LOCKOUT_DURATION . " 分钟"
]);
}
$remaining = self::MAX_ATTEMPTS - $attempts;
return back()->withErrors([
'password' => "密码错误,还有 {$remaining} 次尝试机会"
]);
}
}
渐进式锁定时间(更安全)
实现策略
<?php
function getLockoutDuration($attempts) {
// 第5次失败:15分钟;第10次:1小时;第15次+:24小时
if ($attempts >= 15) return 24 * 3600; // 24小时
if ($attempts >= 10) return 3600; // 1小时
return 15 * 60; // 15分钟
}
function authenticate($username, $password) {
$user = getUserByUsername($username);
// 检查锁定
if ($user['locked_until'] && strtotime($user['locked_until']) > time()) {
return ['success' => false, 'message' => '账号已锁定'];
}
if (verifyPassword($password, $user['password_hash'])) {
resetAttempts($user['id']);
return ['success' => true];
} else {
$attempts = incrementAndGetAttempts($user['id']);
// 计算新的锁定时间
$lockDuration = getLockoutDuration($attempts);
lockUser($user['id'], $lockDuration);
return ['success' => false, 'attempts' => $attempts];
}
}
?>
手动解锁 + 自动检测
管理员端手动解锁
<?php
// 管理员手动解锁
function adminUnlock($userId) {
DB::query("UPDATE users SET login_attempts = 0, locked_until = NULL,
locked_by_admin = 0, unlock_reason = NULL
WHERE id = ?", [$userId]);
}
// 自动检测脚本(Cron Job)
// 每分钟运行一次:reset_locked_accounts.php
function autoUnlockAccounts() {
// 解锁所有已过期的锁定
DB::query("UPDATE users SET login_attempts = 0, locked_until = NULL
WHERE locked_until IS NOT NULL
AND locked_until <= NOW()
AND locked_by_admin = 0");
echo "已解锁 " . DB::affectedRows() . " 个账号";
}
?>
# crontab配置
*/1 * * * * php /path/to/reset_locked_accounts.php
最佳实践建议
安全性增强
<?php
// 添加CAPTCHA验证(推荐)
if ($attempts >= 3) {
require_captcha(); // 要求输入验证码
}
// IP限制(防止暴力破解)
$ip = $_SERVER['REMOTE_ADDR'];
$ipAttempts = Cache::get('ip_attempts_' . $ip, 0);
if ($ipAttempts > 20) { // 同一IP尝试20次
blockIp($ip, 30); // 封锁30分钟
}
?>
用户通知
<?php
// 当解锁时通知用户
function notifyUserOnUnlock($userId) {
$user = getUserById($userId);
if ($user['locked_until'] && strtotime($user['locked_until']) <= time()) {
sendEmail($user['email'], '账号已解锁', '您的账号已自动解锁');
}
}
// 在检查锁定时调用
if ($user['locked_until'] && strtotime($user['locked_until']) <= time()) {
autoUnlockUser($userId);
notifyUserOnUnlock($userId);
}
?>
安全配置建议
<?php
// config.php
return [
'login' => [
'max_attempts' => 5, // 最大尝试次数
'lockout_time' => 15, // 锁定时间(分钟)
'attempts_window' => 30, // 计数窗口(分钟)
'unlock_after' => 180, // 管理员手动解锁后自动解锁(分钟)
'send_notification' => true, // 是否发送通知
]
];
?>
推荐方案
对于大多数项目,方案一(数据库时间戳) + 方案四(定时任务清理) 的组合是最实用的:
- 数据库存储:
login_attempts,locked_until字段 - 登录时自动检测时间是否过期
- 配合Cron Job清理过期的锁定记录
- 管理员可手动解锁特殊账号
这样既简单可靠,又不会增加额外的外部依赖(如Redis)。