119 lines
5.4 KiB
PHP
119 lines
5.4 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
function portalEnabled(): bool { return getenv('PORTAL_SSO_ENABLED') === '1'; }
|
|
|
|
function portalPublic(): string
|
|
{
|
|
$url = rtrim((string)getenv('PORTAL_PUBLIC_URL'), '/');
|
|
if (!preg_match('#^https?://[a-zA-Z0-9.:-]+$#D', $url)) {
|
|
throw new RuntimeException('Portal URL unavailable');
|
|
}
|
|
return $url;
|
|
}
|
|
|
|
function portalFail(int $status, string $message): never
|
|
{
|
|
http_response_code($status);
|
|
header('Content-Type: text/html; charset=utf-8');
|
|
header('Cache-Control: no-store');
|
|
header('Referrer-Policy: no-referrer');
|
|
echo '<!doctype html><meta charset="utf-8"><title>金牛贷款档案</title><p>' . htmlspecialchars($message) . '</p>';
|
|
try { echo '<a href="' . htmlspecialchars(portalPublic(), ENT_QUOTES) . '">返回统一门户</a>'; } catch (Throwable $e) {}
|
|
exit;
|
|
}
|
|
|
|
function portalCall(string $operation, array $payload): array
|
|
{
|
|
$base = rtrim((string)getenv('PORTAL_INTERNAL_URL'), '/');
|
|
$secret = (string)getenv('PORTAL_SSO_SECRET');
|
|
if (!preg_match('#^https?://[a-zA-Z0-9.:-]+$#D', $base) || strlen($secret) < 32) {
|
|
throw new RuntimeException('SSO configuration unavailable');
|
|
}
|
|
$context = stream_context_create(['http' => [
|
|
'method' => 'POST', 'header' => "Content-Type: application/json\r\nAuthorization: Bearer " . $secret,
|
|
'content' => json_encode($payload), 'timeout' => 5, 'follow_location' => 0,
|
|
'ignore_errors' => true,
|
|
]]);
|
|
$body = @file_get_contents($base . '/api/sso/apps/loan-archive/' . $operation, false, $context, 0, 32769);
|
|
if ($body === false || strlen($body) > 32768 || !isset($http_response_header[0])
|
|
|| !preg_match('#^HTTP/\S+ 200\b#', $http_response_header[0])) {
|
|
throw new RuntimeException('Portal verification unavailable');
|
|
}
|
|
$result = json_decode($body, true, 32, JSON_THROW_ON_ERROR);
|
|
if (!is_array($result)) throw new RuntimeException('Invalid identity response');
|
|
return $result;
|
|
}
|
|
|
|
function portalValidate(): void
|
|
{
|
|
static $checked = false;
|
|
if (!portalEnabled() || $checked) return;
|
|
$handle = $_SESSION['portal_handle'] ?? '';
|
|
if (!is_string($handle) || !preg_match('/^[a-f0-9]{64}$/D', $handle)) {
|
|
portalFail(401, '请从统一门户进入贷款档案系统。');
|
|
}
|
|
try { $result = portalCall('introspect', ['session_handle' => $handle]); }
|
|
catch (Throwable $e) { portalFail(503, '暂时无法验证登录,请稍后重试。'); }
|
|
$identity = $result['identity'] ?? [];
|
|
if (empty($result['active']) || ($identity['empid'] ?? '') !== ($_SESSION['portal_empid'] ?? '')
|
|
|| ($identity['local_uid'] ?? 0) !== ($_SESSION['uid'] ?? 0)) {
|
|
$_SESSION = [];
|
|
session_regenerate_id(true);
|
|
portalFail(401, '登录或授权已失效,请重新进入统一门户。');
|
|
}
|
|
$checked = true;
|
|
}
|
|
|
|
function portalCallback(): never
|
|
{
|
|
if (!portalEnabled()) portalFail(404, '统一登录未启用。');
|
|
header('Cache-Control: no-store');
|
|
header('Referrer-Policy: no-referrer');
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'GET') portalFail(405, 'Method not allowed');
|
|
if (!isset($_GET['ticket'])) {
|
|
session_regenerate_id(true);
|
|
$verifier = bin2hex(random_bytes(32));
|
|
$_SESSION['portal_verifier'] = $verifier;
|
|
$_SESSION['portal_started'] = time();
|
|
$_SESSION['portal_theme'] = ($_GET['theme'] ?? '') === 'night' ? 'night' : 'day';
|
|
header('Location: ' . portalPublic() . '/api/sso/apps/loan-archive/authorize?challenge=' . hash('sha256', $verifier), true, 303);
|
|
exit;
|
|
}
|
|
$ticket = $_GET['ticket'];
|
|
$verifier = $_SESSION['portal_verifier'] ?? '';
|
|
if (!is_string($ticket) || !preg_match('/^[a-f0-9]{64}$/D', $ticket)
|
|
|| !preg_match('/^[a-f0-9]{64}$/D', $verifier)
|
|
|| time() - (int)($_SESSION['portal_started'] ?? 0) > 120) {
|
|
portalFail(401, '登录请求已失效,请从门户重新进入。');
|
|
}
|
|
unset($_SESSION['portal_verifier'], $_SESSION['portal_started']);
|
|
try {
|
|
$result = portalCall('exchange', ['ticket' => $ticket, 'verifier' => $verifier]);
|
|
$who = $result['identity'] ?? [];
|
|
$uid = $who['local_uid'] ?? 0;
|
|
$empid = $who['empid'] ?? '';
|
|
$handle = $result['session_handle'] ?? '';
|
|
if (!is_int($uid) || $uid < 1 || !preg_match('/^[a-f0-9-]{36}$/D', $empid)
|
|
|| !preg_match('/^[a-f0-9]{64}$/D', $handle)) throw new RuntimeException('Invalid identity');
|
|
$db = db();
|
|
$stmt = $db->prepare('SELECT id FROM users WHERE id=?');
|
|
$stmt->execute([$uid]);
|
|
if (!$stmt->fetch()) throw new DomainException('Local account missing');
|
|
$stmt = $db->prepare('INSERT OR IGNORE INTO portal_identity(uid, empid) VALUES (?,?)');
|
|
$stmt->execute([$uid, $empid]);
|
|
$stmt = $db->prepare('SELECT empid FROM portal_identity WHERE uid=?');
|
|
$stmt->execute([$uid]);
|
|
if ($stmt->fetchColumn() !== $empid) throw new DomainException('Binding mismatch');
|
|
$theme = $_SESSION['portal_theme'] ?? 'day';
|
|
$_SESSION = ['uid' => $uid, 'portal_empid' => $empid, 'portal_handle' => $handle];
|
|
session_regenerate_id(true);
|
|
header('Location: /index.php?action=workbench&theme=' . $theme, true, 303);
|
|
exit;
|
|
} catch (Throwable $e) {
|
|
$_SESSION = [];
|
|
session_regenerate_id(true);
|
|
portalFail(401, '无法进入,请检查门户授权、账号绑定及子系统可用状态。');
|
|
}
|
|
}
|