Laravel 12 登录失败锁定账户、IP及解锁实现方案

发布于:2025-05-09 ⋅ 阅读:(9) ⋅ 点赞:(0)

Laravel 12 登录失败锁定账户、IP及解锁实现方案

1. 数据库准备

首先,我们需要在用户表中添加相关字段:

// 创建迁移文件
php artisan make:migration add_lock_fields_to_users_table
// 迁移文件内容
public function up()
{
    Schema::table('users', function (Blueprint $table) {
        $table->integer('login_attempts')->default(0);
        $table->timestamp('locked_at')->nullable();
        $table->string('locked_reason')->nullable();
    });
    
    // 创建IP锁定表
    Schema::create('ip_lockouts', function (Blueprint $table) {
        $table->id();
        $table->string('ip_address');
        $table->integer('attempts')->default(0);
        $table->timestamp('locked_at')->nullable();
        $table->timestamp('unlock_at')->nullable();
        $table->timestamps();
        
        $table->index('ip_address');
    });
}

2. 实现账户锁定逻辑

2.1 创建登录失败监听器

php artisan make:listener RecordFailedLoginAttempt
// app/Listeners/RecordFailedLoginAttempt.php
public function handle(Failed $event)
{
    $user = $event->user;
    $ip = request()->ip();
    
    // 更新用户失败尝试次数
    if ($user) {
        $user->increment('login_attempts');
        
        // 检查是否需要锁定账户
        if ($user->login_attempts >= config('auth.lockout.max_attempts')) {
            $user->update([
                'locked_at' => now(),
                'locked_reason' => 'Too many failed login attempts',
            ]);
            
            // 触发账户锁定事件
            event(new AccountLocked($user));
        }
    }
    
    // 记录IP失败尝试
    $ipLockout = IpLockout::firstOrCreate(['ip_address' => $ip]);
    $ipLockout->increment('attempts');
    
    // 检查是否需要锁定IP
    if ($ipLockout->attempts >= config('auth.lockout.ip_max_attempts')) {
        $ipLockout->update([
            'locked_at' => now(),
            'unlock_at' => now()->addMinutes(config('auth.lockout.ip_lockout_minutes'))
        ]);
        
        // 触发IP锁定事件
        event(new IpLocked($ip));
    }
}

2.2 创建账户锁定中间件

php artisan make:middleware CheckAccountStatus
// app/Http/Middleware/CheckAccountStatus.php
public function handle($request, Closure $next)
{
    if (auth()->check()) {
        $user = auth()->user();
        
        // 检查账户是否被锁定
        if ($user->locked_at) {
            auth()->logout();
            return redirect()->route('login')
                ->with('error', '您的账户已被锁定。' . ($user->locked_reason ? '原因: ' . $user->locked_reason : ''));
        }
    }
    
    // 检查IP是否被锁定
    $ipLockout = IpLockout::where('ip_address', $request->ip())
        ->whereNotNull('locked_at')
        ->where('unlock_at', '>', now())
        ->first();
        
    if ($ipLockout) {
        return redirect()->route('login')
            ->with('error', '您的IP已被锁定,请 ' . $ipLockout->unlock_at->diffForHumans() . ' 后再试');
    }
    
    return $next($request);
}

3. 实现解锁功能

3.1 自动解锁

创建定时任务检查并解锁过期的锁定:

// app/Console/Kernel.php
protected function schedule(Schedule $schedule)
{
    $schedule->call(function () {
        // 解锁过期账户
        User::whereNotNull('locked_at')
            ->where('locked_at', '<', now()->subMinutes(config('auth.lockout.lockout_minutes')))
            ->update([
                'locked_at' => null,
                'locked_reason' => null,
                'login_attempts' => 0
            ]);
            
        // 解锁过期IP
        IpLockout::whereNotNull('locked_at')
            ->where('unlock_at', '<', now())
            ->update([
                'locked_at' => null,
                'unlock_at' => null,
                'attempts' => 0
            ]);
    })->everyFiveMinutes();
}

3.2 管理员手动解锁

创建解锁控制器:

php artisan make:controller Admin/UnlockController
// app/Http/Controllers/Admin/UnlockController.php
public function unlockAccount(User $user)
{
    $user->update([
        'locked_at' => null,
        'locked_reason' => null,
        'login_attempts' => 0
    ]);
    
    return back()->with('success', '账户已解锁');
}

public function unlockIp($ip)
{
    IpLockout::where('ip_address', $ip)->update([
        'locked_at' => null,
        'unlock_at' => null,
        'attempts' => 0
    ]);
    
    return back()->with('success', 'IP已解锁');
}

4. 配置

config/auth.php 中添加锁定配置:

'lockout' => [
    'max_attempts' => 5, // 账户锁定前最大尝试次数
    'lockout_minutes' => 30, // 账户锁定时间(分钟)
    'ip_max_attempts' => 10, // IP锁定前最大尝试次数
    'ip_lockout_minutes' => 60, // IP锁定时间(分钟)
],

5. 注册中间件和事件

app/Http/Kernel.php 中注册中间件:

protected $middlewareGroups = [
    'web' => [
        // ...
        \App\Http\Middleware\CheckAccountStatus::class,
    ],
];

注册事件监听器:

// app/Providers/EventServiceProvider.php
protected $listen = [
    Failed::class => [
        RecordFailedLoginAttempt::class,
    ],
    AccountLocked::class => [
        SendAccountLockedNotification::class,
    ],
    IpLocked::class => [
        SendIpLockedNotification::class,
    ],
];

6. 前端显示

在登录视图中显示锁定信息:

@if(session('error'))
    <div class="alert alert-danger">
        {{ session('error') }}
    </div>
@endif

@if($errors->has('locked'))
    <div class="alert alert-danger">
        {{ $errors->first('locked') }}
    </div>
@endif

7. 测试

可以使用以下方法测试功能:

  1. 故意输入错误密码达到锁定阈值
  2. 检查数据库锁定状态
  3. 尝试登录已锁定账户/IP
  4. 等待自动解锁或使用管理员解锁
  5. 验证解锁后能否正常登录

8. 扩展功能建议

  1. 解锁验证码‌: 在接近锁定阈值时要求验证码
  2. 多因素认证‌: 锁定后通过邮箱或手机验证解锁
  3. IP白名单‌: 允许特定IP绕过锁定限制
  4. 锁定日志‌: 记录所有锁定和解锁操作
  5. 通知系统‌: 锁定后发送邮件通知管理员

这个实现方案提供了完整的账户和IP锁定机制,包括自动和手动解锁功能,可以根据实际需求进行调整。


网站公告

今日签到

点亮在社区的每一天
去签到