check()) { return redirect()->route('admin.home'); } return view('admin.login'); } public function home() { return view('admin.shell'); } public function login(Request $request, AdminGoogle2fa $google2fa): JsonResponse { try { $credentials = $request->validate([ 'username' => 'required|string|min:2|max:64', 'password' => 'required|string|min:6|max:128', 'GACode' => 'nullable|string|max:16', ], [ 'username.required' => '请输入用户名', 'password.required' => '请输入密码', ]); } catch (ValidationException $e) { return response()->json([ 'code' => 1, 'msg' => collect($e->errors())->flatten()->implode('
'), ]); } $throttleKey = $this->throttleKey($request); if (RateLimiter::tooManyAttempts($throttleKey, self::MAX_ATTEMPTS)) { $seconds = RateLimiter::availableIn($throttleKey); return response()->json([ 'code' => 1, 'msg' => '登陆失败次数过多,请'.$seconds.'秒后再重试', ]); } // Account-level lock: if the admin account is locked due to too many // consecutive wrong passwords, reject the login regardless of IP. $admin = Admin::query()->where('username', $credentials['username'])->first(); if ($admin && $admin->isLocked()) { return response()->json([ 'code' => 1, 'msg' => '账号已被封锁(连续输错密码 '.self::MAX_ATTEMPTS.' 次),请联系超级管理员解除', ]); } if (! Auth::guard('admin')->attempt( ['username' => $credentials['username'], 'password' => $credentials['password']], false )) { RateLimiter::hit($throttleKey, self::DECAY_SECONDS); // Track consecutive wrong passwords on the account itself. if ($admin) { $justLocked = $admin->recordFailedLogin(self::MAX_ATTEMPTS); if ($justLocked) { SystemLog::record( $admin, 'admin', SystemLog::ACTION_ADMIN_LOCKED, '管理员「'.$admin->username.'」连续输错密码 '.self::MAX_ATTEMPTS.' 次,账号被自动封锁', $request, ); return response()->json([ 'code' => 1, 'msg' => '密码连续输错 '.self::MAX_ATTEMPTS.' 次,账号已被封锁,请联系超级管理员解除', ]); } $remaining = self::MAX_ATTEMPTS - (int) $admin->fresh()->login_attempts; if ($remaining > 0) { return response()->json([ 'code' => 1, 'msg' => '用户名或密码错误(剩余 '.$remaining.' 次尝试机会)', ]); } } return response()->json(['code' => 1, 'msg' => '用户名或密码错误']); } /** @var Admin $user */ $user = Auth::guard('admin')->user(); if ((int) $user->status !== 1) { Auth::guard('admin')->logout(); RateLimiter::hit($throttleKey, self::DECAY_SECONDS); return response()->json(['code' => 1, 'msg' => '用户已被禁用']); } if ((int) $user->google_auth_open === 1) { $code = (string) ($credentials['GACode'] ?? ''); if ($code === '') { Auth::guard('admin')->logout(); return response()->json(['code' => 1, 'msg' => '请输入谷歌验证码!']); } if (! $google2fa->verify((string) $user->google_secret, $code)) { Auth::guard('admin')->logout(); RateLimiter::hit($throttleKey, self::DECAY_SECONDS); return response()->json(['code' => 1, 'msg' => '谷歌验证码不正确!']); } } RateLimiter::clear($throttleKey); $user->clearLoginAttempts(); $request->session()->regenerate(); $user->forceFill(['last_ip' => VisitorIp::fromRequest($request)])->save(); return response()->json([ 'code' => 0, 'msg' => '登录成功', 'data' => route('admin.home'), ]); } public function logout(Request $request) { Auth::guard('admin')->logout(); $request->session()->invalidate(); $request->session()->regenerateToken(); return redirect()->route('admin.login'); } private function throttleKey(Request $request): string { return Str::transliterate(Str::lower((string) $request->input('username')).'|'.VisitorIp::fromRequest($request)); } }