feat(堡垒机检测): 支持HTTP与命令行检测并可自动刷新失效Token
- 合并检测接口到 BastionAccountController - 新增 bastion:check-login 命令,支持 --refresh-invalid - 检测失效后立即刷新并复检状态
This commit is contained in:
parent
550ac11789
commit
e10f050dfc
177
app/Console/Commands/CheckBastionLoginCommand.php
Normal file
177
app/Console/Commands/CheckBastionLoginCommand.php
Normal file
@ -0,0 +1,177 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\BastionAccount;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Http\Client\RequestException;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class CheckBastionLoginCommand extends Command
|
||||
{
|
||||
protected $signature = 'bastion:check-login {--account_id=} {--refresh-invalid}';
|
||||
|
||||
protected $description = '检测堡垒机账号 token 是否有效(0 有效,-1 无效)';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$query = BastionAccount::query()
|
||||
->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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user