From e10f050dfce36b510c1493d0b3b3d4adfcb44314 Mon Sep 17 00:00:00 2001 From: Boen_Shi Date: Thu, 30 Apr 2026 14:21:35 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=A0=A1=E5=9E=92=E6=9C=BA=E6=A3=80?= =?UTF-8?q?=E6=B5=8B):=20=E6=94=AF=E6=8C=81HTTP=E4=B8=8E=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=E8=A1=8C=E6=A3=80=E6=B5=8B=E5=B9=B6=E5=8F=AF=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=88=B7=E6=96=B0=E5=A4=B1=E6=95=88Token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 合并检测接口到 BastionAccountController - 新增 bastion:check-login 命令,支持 --refresh-invalid - 检测失效后立即刷新并复检状态 --- .../Commands/CheckBastionLoginCommand.php | 177 ++++++++++++++++++ .../Api/BastionAccountController.php | 158 +++++++++++++++- 2 files changed, 333 insertions(+), 2 deletions(-) create mode 100644 app/Console/Commands/CheckBastionLoginCommand.php diff --git a/app/Console/Commands/CheckBastionLoginCommand.php b/app/Console/Commands/CheckBastionLoginCommand.php new file mode 100644 index 0000000..8efb8f6 --- /dev/null +++ b/app/Console/Commands/CheckBastionLoginCommand.php @@ -0,0 +1,177 @@ +where('is_active', true) + ->whereNotNull('usm') + ->where('usm', '!=', '') + ->whereNotNull('usm_authentication') + ->where('usm_authentication', '!=', ''); + + $accountId = $this->option('account_id'); + if ($accountId !== null && $accountId !== '') { + $query->where('id', (int) $accountId); + } + + $accounts = $query->orderBy('id')->get(); + if ($accounts->isEmpty()) { + $this->line('[]'); + + return self::SUCCESS; + } + + $rows = []; + foreach ($accounts as $account) { + $status = $this->checkAccountTokenStatus((string) $account->usm_authentication, (string) $account->usm); + $refreshed = false; + if ($status === -1 && $this->option('refresh-invalid')) { + $refreshed = $this->refreshAccountToken($account); + if ($refreshed) { + $account->refresh(); + $status = $this->checkAccountTokenStatus((string) $account->usm_authentication, (string) $account->usm); + } + } + + $rows[] = [ + 'id' => $account->id, + 'username' => $account->username, + 'status' => $status, + 'refreshed' => $refreshed, + ]; + } + + $this->line(json_encode($rows, JSON_UNESCAPED_UNICODE)); + + return self::SUCCESS; + } + + private function checkAccountTokenStatus(string $usmAuthentication, string $usm): int + { + $url = 'https://172.16.254.2/index.php/extend/check_login'; + $cookie = sprintf( + 'LANG=zh; USM-AUTHENTICATION=%s; USM=%s; LOGON=1; AUTH_METHOD=oauth2', + $usmAuthentication, + $usm + ); + + try { + $response = Http::timeout(10) + ->withOptions(['verify' => false]) + ->withHeaders([ + 'Cookie' => $cookie, + 'Accept' => '*/*', + ]) + ->get($url); + } catch (ConnectionException) { + return -1; + } + + if (! $response->successful()) { + return -1; + } + + return trim((string) $response->body()) === '0' ? 0 : -1; + } + + private function refreshAccountToken(BastionAccount $account): bool + { + $baseUrl = (string) config('services.bastion_token.base_url'); + if ($baseUrl === '') { + return false; + } + + $submitEndpoint = (string) config('services.bastion_token.submit_endpoint', '/bastion_token'); + $statusEndpoint = (string) config('services.bastion_token.status_endpoint', '/bastion_token/{task_id}'); + $timeout = (int) config('services.bastion_token.timeout', 30); + $service = (string) config('services.bastion_token.service', 'https://myapp.cdu.edu.cn/index.html'); + $verifySsl = (bool) config('services.bastion_token.verify_ssl', false); + + try { + $submitResponse = Http::baseUrl($baseUrl) + ->acceptJson() + ->timeout($timeout) + ->retry(2, 300, throw: false) + ->post($submitEndpoint, [ + 'username' => $account->username, + 'password' => $account->password, + 'service' => $service, + 'verify_ssl' => $verifySsl, + ]); + } catch (ConnectionException|RequestException) { + return false; + } + + if (! $submitResponse->successful()) { + return false; + } + + $taskId = (string) data_get($submitResponse->json(), 'task_id', ''); + if ($taskId === '') { + return false; + } + + $maxPoll = 20; + for ($i = 0; $i < $maxPoll; $i++) { + usleep(500000); + $statusUrl = str_replace('{task_id}', $taskId, $statusEndpoint); + try { + $statusResponse = Http::baseUrl($baseUrl) + ->acceptJson() + ->timeout($timeout) + ->retry(2, 300, throw: false) + ->get($statusUrl); + } catch (ConnectionException|RequestException) { + continue; + } + + if (! $statusResponse->successful()) { + continue; + } + + $taskResult = $statusResponse->json(); + $status = strtolower((string) data_get($taskResult, 'status', 'pending')); + if ($status === 'pending') { + continue; + } + + if ($status === 'error') { + return false; + } + + $usmAuthentication = (string) data_get($taskResult, 'data.bastion.token_cookies.USM-AUTHENTICATION', ''); + $usm = (string) data_get($taskResult, 'data.bastion.token_cookies.USM', ''); + if ($usmAuthentication === '' || $usm === '') { + $usmAuthentication = (string) data_get($taskResult, 'data.USM-AUTHENTICATION', $usmAuthentication); + $usm = (string) data_get($taskResult, 'data.USM', $usm); + } + if ($usmAuthentication === '' || $usm === '') { + return false; + } + + $account->update([ + 'usm_authentication' => $usmAuthentication, + 'usm' => $usm, + 'last_token_refreshed_at' => now(), + ]); + + return true; + } + + return false; + } +} diff --git a/app/Http/Controllers/Api/BastionAccountController.php b/app/Http/Controllers/Api/BastionAccountController.php index 0c257ff..0f9ebe6 100644 --- a/app/Http/Controllers/Api/BastionAccountController.php +++ b/app/Http/Controllers/Api/BastionAccountController.php @@ -7,9 +7,9 @@ use App\Http\Requests\StoreBastionAccountRequest; use App\Http\Requests\UpdateBastionAccountRequest; use App\Models\BastionAccount; use hg\apidoc\annotation as Apidoc; -use Illuminate\Http\JsonResponse; use Illuminate\Http\Client\ConnectionException; use Illuminate\Http\Client\RequestException; +use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Http; @@ -22,7 +22,7 @@ class BastionAccountController extends Controller { $this->middleware('auth:api'); $this->middleware('permission:platform.accounts.view,api')->only(['index', 'show']); - $this->middleware('permission:platform.accounts.manage,api')->only(['store', 'update', 'destroy', 'refreshToken', 'refreshTokenStatus']); + $this->middleware('permission:platform.accounts.manage,api')->only(['store', 'update', 'destroy', 'refreshToken', 'refreshTokenStatus', 'checkBastionLogin']); } #[Apidoc\Title('账号列表'), Apidoc\Method('GET'), Apidoc\Url('/accounts')] @@ -252,4 +252,158 @@ class BastionAccountController extends Controller { return 'bastion_token_task:'.$taskId; } + + #[Apidoc\Title('检测堡垒机登录有效性'), Apidoc\Method('GET'), Apidoc\Url('/accounts/check-login')] + public function checkBastionLogin(Request $request): JsonResponse + { + $autoRefresh = $request->boolean('auto_refresh', false); + $accounts = BastionAccount::query() + ->where('is_active', true) + ->whereNotNull('usm') + ->where('usm', '!=', '') + ->whereNotNull('usm_authentication') + ->where('usm_authentication', '!=', '') + ->orderBy('id') + ->get(); + + $result = []; + foreach ($accounts as $account) { + $status = $this->checkAccountTokenStatus((string) $account->usm_authentication, (string) $account->usm); + $refreshed = false; + if ($status === -1 && $autoRefresh) { + $refreshed = $this->refreshAccountTokenDirectly($account); + if ($refreshed) { + $account->refresh(); + $status = $this->checkAccountTokenStatus((string) $account->usm_authentication, (string) $account->usm); + } + } + + $result[] = [ + 'id' => $account->id, + 'name' => $account->name, + 'username' => $account->username, + 'status' => $status, + 'refreshed' => $refreshed, + ]; + } + + return response()->json([ + 'code' => 0, + 'message' => 'ok', + 'data' => $result, + ]); + } + + private function checkAccountTokenStatus(string $usmAuthentication, string $usm): int + { + $url = 'https://172.16.254.2/index.php/extend/check_login'; + $cookie = sprintf( + 'LANG=zh; USM-AUTHENTICATION=%s; USM=%s; LOGON=1; AUTH_METHOD=oauth2', + $usmAuthentication, + $usm + ); + + try { + $response = Http::timeout(10) + ->withOptions(['verify' => false]) + ->withHeaders([ + 'Cookie' => $cookie, + 'Accept' => '*/*', + ]) + ->get($url); + } catch (ConnectionException) { + return -1; + } + + if (! $response->successful()) { + return -1; + } + + return trim((string) $response->body()) === '0' ? 0 : -1; + } + + private function refreshAccountTokenDirectly(BastionAccount $account): bool + { + $baseUrl = (string) config('services.bastion_token.base_url'); + if ($baseUrl === '') { + return false; + } + + $submitEndpoint = (string) config('services.bastion_token.submit_endpoint', '/bastion_token'); + $statusEndpoint = (string) config('services.bastion_token.status_endpoint', '/bastion_token/{task_id}'); + $timeout = (int) config('services.bastion_token.timeout', 30); + $service = (string) config('services.bastion_token.service', 'https://myapp.cdu.edu.cn/index.html'); + $verifySsl = (bool) config('services.bastion_token.verify_ssl', false); + + try { + $submitResponse = Http::baseUrl($baseUrl) + ->acceptJson() + ->timeout($timeout) + ->retry(2, 300, throw: false) + ->post($submitEndpoint, [ + 'username' => $account->username, + 'password' => $account->password, + 'service' => $service, + 'verify_ssl' => $verifySsl, + ]); + } catch (ConnectionException|RequestException) { + return false; + } + + if (! $submitResponse->successful()) { + return false; + } + + $taskId = (string) data_get($submitResponse->json(), 'task_id', ''); + if ($taskId === '') { + return false; + } + + for ($i = 0; $i < 20; $i++) { + usleep(500000); + $statusUrl = str_replace('{task_id}', $taskId, $statusEndpoint); + try { + $statusResponse = Http::baseUrl($baseUrl) + ->acceptJson() + ->timeout($timeout) + ->retry(2, 300, throw: false) + ->get($statusUrl); + } catch (ConnectionException|RequestException) { + continue; + } + + if (! $statusResponse->successful()) { + continue; + } + + $taskResult = $statusResponse->json(); + $status = strtolower((string) data_get($taskResult, 'status', 'pending')); + if ($status === 'pending') { + continue; + } + if ($status === 'error') { + return false; + } + + $usmAuthentication = (string) data_get($taskResult, 'data.bastion.token_cookies.USM-AUTHENTICATION', ''); + $usm = (string) data_get($taskResult, 'data.bastion.token_cookies.USM', ''); + if ($usmAuthentication === '' || $usm === '') { + $usmAuthentication = (string) data_get($taskResult, 'data.USM-AUTHENTICATION', $usmAuthentication); + $usm = (string) data_get($taskResult, 'data.USM', $usm); + } + if ($usmAuthentication === '' || $usm === '') { + return false; + } + + $account->update([ + 'usm_authentication' => $usmAuthentication, + 'usm' => $usm, + 'last_token_refreshed_at' => now(), + ]); + + return true; + } + + return false; + } }