PHP项目账号锁定如何自动解锁

wen PHP项目 29

本文目录导读:

PHP项目账号锁定如何自动解锁

  1. 方案一:基于时间的自动解锁(推荐)
  2. 方案二:缓存自动解锁(Laravel示例)
  3. 方案三:渐进式锁定时间(更安全)
  4. 方案四:手动解锁 + 自动检测
  5. 最佳实践建议
  6. 推荐方案

在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,   // 是否发送通知
    ]
];
?>

推荐方案

对于大多数项目,方案一(数据库时间戳) + 方案四(定时任务清理) 的组合是最实用的:

  1. 数据库存储login_attempts, locked_until 字段
  2. 登录时自动检测时间是否过期
  3. 配合Cron Job清理过期的锁定记录
  4. 管理员可手动解锁特殊账号

这样既简单可靠,又不会增加额外的外部依赖(如Redis)。

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