throttleKey($request); // 锁定检查:10 分钟内错误满 5 次即锁定,提示等待时长 if (RateLimiter::tooManyAttempts($key, self::MAX_ATTEMPTS)) { $seconds = RateLimiter::availableIn($key); $request->session()->forget('captcha'); throw ValidationException::withMessages([ 'email' => '登录尝试过于频繁,出于安全考虑已临时锁定。请在约 '.ceil($seconds / 60).' 分钟('.ceil($seconds).' 秒)后重试。', ]); } $credentials = $request->validate([ 'email' => ['required', 'email'], 'password' => ['required', 'string'], 'captcha' => ['required', 'string', function ($attribute, $value, $fail) use ($request) { if (strtolower(trim((string) $value)) !== strtolower((string) $request->session()->get('captcha', ''))) { $fail('图形验证码不正确,请重新输入。'); } }], ]); if (Auth::attempt(['email' => $credentials['email'], 'password' => $credentials['password']], $request->boolean('remember'))) { RateLimiter::clear($key); $request->session()->forget('captcha'); $request->session()->regenerate(); return redirect()->intended('/'); } // 失败:记录一次错误(10 分钟滚动窗口),并给出剩余次数提醒 RateLimiter::hit($key, self::DECAY_SECONDS); $attempts = RateLimiter::attempts($key); $left = max(0, self::MAX_ATTEMPTS - $attempts); $request->session()->forget('captcha'); $msg = '账号或密码不正确。'; if ($left > 0) { $msg .= "(已错误 {$attempts} 次,再错 {$left} 次将锁定 10 分钟)"; } else { $msg .= '(错误次数过多,已锁定 10 分钟)'; } return back()->withErrors([ 'email' => $msg, ])->onlyInput('email'); } protected function throttleKey(Request $request): string { return 'login:'.strtolower($request->input('email', '')).':'.$request->ip(); } /** * 退出登录。定义为控制器方法而非闭包, * 以保证生产环境 `php artisan route:cache` 可用。 */ public function destroy(Request $request): RedirectResponse { Auth::guard('web')->logout(); $request->session()->invalidate(); $request->session()->regenerateToken(); return redirect('/'); } }