Files
ai-web/app/Http/Controllers/Admin/AuthController.php
zhongjy001 4c9790d140
Some checks failed
Tests / PHP 8.2 (push) Has been cancelled
Tests / PHP 8.3 (push) Has been cancelled
Tests / PHP 8.4 (push) Has been cancelled
css
2026-02-14 02:28:10 +08:00

162 lines
5.3 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
class AuthController extends Controller
{
private const LOGIN_MAX_ATTEMPTS = 5;
private const LOGIN_DECAY_SECONDS = 120;
public function showLogin(): View
{
return view('admin.auth.login');
}
/**
* @throws ValidationException
*/
public function login(Request $request): RedirectResponse
{
$payload = $request->validate([
'username' => ['required', 'string', 'max:100'],
'password' => ['required', 'string', 'max:255'],
'captcha' => ['required', 'string', 'size:5'],
], [
'username.required' => '请输入后台账号。',
'password.required' => '请输入后台密码。',
'captcha.required' => '请输入验证码。',
'captcha.size' => '验证码格式不正确。',
]);
$rateLimiterKey = $this->loginRateLimiterKey($request, $payload['username']);
if (RateLimiter::tooManyAttempts($rateLimiterKey, self::LOGIN_MAX_ATTEMPTS)) {
$seconds = RateLimiter::availableIn($rateLimiterKey);
throw ValidationException::withMessages([
'username' => "尝试次数过多,请在 {$seconds} 秒后重试。",
]);
}
if (! $this->isCaptchaValid($request, $payload['captcha'])) {
RateLimiter::hit($rateLimiterKey, self::LOGIN_DECAY_SECONDS);
throw ValidationException::withMessages([
'captcha' => '验证码错误,请重试。',
]);
}
if (! $this->isCredentialValid($payload['username'], $payload['password'])) {
RateLimiter::hit($rateLimiterKey, self::LOGIN_DECAY_SECONDS);
throw ValidationException::withMessages([
'username' => '账号或密码错误。',
]);
}
RateLimiter::clear($rateLimiterKey);
$request->session()->regenerate();
$request->session()->put('admin_authenticated', true);
$request->session()->put('admin_username', $payload['username']);
$request->session()->forget('admin_captcha_hash');
return redirect()->route('admin.dashboard');
}
public function logout(Request $request): RedirectResponse
{
$request->session()->forget(['admin_authenticated', 'admin_username', 'admin_captcha_hash']);
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect()->route('admin.login')->with('status', '已安全退出后台。');
}
public function captcha(Request $request): Response
{
$captchaText = strtoupper(Str::random(5));
$request->session()->put('admin_captcha_hash', hash('sha256', strtolower($captchaText)));
$imageWidth = 140;
$imageHeight = 46;
$image = imagecreatetruecolor($imageWidth, $imageHeight);
$background = imagecolorallocate($image, 241, 245, 249);
$textColor = imagecolorallocate($image, 15, 23, 42);
$lineColor = imagecolorallocate($image, 148, 163, 184);
$noiseColor = imagecolorallocate($image, 100, 116, 139);
imagefilledrectangle($image, 0, 0, $imageWidth, $imageHeight, $background);
for ($index = 0; $index < 4; $index++) {
imageline(
$image,
random_int(0, $imageWidth),
random_int(0, $imageHeight),
random_int(0, $imageWidth),
random_int(0, $imageHeight),
$lineColor,
);
}
for ($index = 0; $index < 90; $index++) {
imagesetpixel($image, random_int(0, $imageWidth - 1), random_int(0, $imageHeight - 1), $noiseColor);
}
imagestring($image, 5, 24, 14, $captchaText, $textColor);
ob_start();
imagepng($image);
$binary = (string) ob_get_clean();
imagedestroy($image);
return response($binary, 200, [
'Content-Type' => 'image/png',
'Cache-Control' => 'no-store, no-cache, must-revalidate, max-age=0',
'Pragma' => 'no-cache',
'Expires' => '0',
]);
}
private function isCredentialValid(string $username, string $password): bool
{
$configuredUser = (string) config('app.admin_user', 'admin');
$configuredPassword = (string) config('app.admin_password', 'change-me');
return hash_equals($configuredUser, $username)
&& hash_equals($configuredPassword, $password);
}
private function isCaptchaValid(Request $request, string $input): bool
{
$storedHash = (string) $request->session()->get('admin_captcha_hash', '');
$request->session()->forget('admin_captcha_hash');
if ($storedHash === '') {
return false;
}
return hash_equals($storedHash, hash('sha256', strtolower(trim($input))));
}
private function loginRateLimiterKey(Request $request, string $username): string
{
return Str::lower($username) . '|' . $request->ip();
}
}