89 lines
3.4 KiB
PHP
89 lines
3.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace app\adminapi\controller;
|
|
|
|
use app\adminapi\service\iam\IamLoginService;
|
|
use think\facade\Log;
|
|
|
|
/** Dedicated optional endpoints: existing account and WeCom actions are untouched. */
|
|
class IamController extends BaseAdminController
|
|
{
|
|
public array $notNeedLogin = ['config', 'start', 'callback', 'exchange'];
|
|
private const COOKIE = 'ZYT_IAM_BROWSER';
|
|
|
|
public function config()
|
|
{
|
|
return $this->data(IamLoginService::settings())->header(['Cache-Control' => 'no-store']);
|
|
}
|
|
|
|
public function start()
|
|
{
|
|
try {
|
|
$service = new IamLoginService();
|
|
$browser = $this->browser();
|
|
if ($browser === '') {
|
|
$browser = bin2hex(random_bytes(32));
|
|
}
|
|
setcookie(self::COOKIE, $browser, ['expires' => time() + 600, 'path' => '/adminapi/iam', 'secure' => true, 'httponly' => true, 'samesite' => 'Lax']);
|
|
return redirect($service->start($browser, $this->request->ip()))->header($this->privateHeaders());
|
|
} catch (\Throwable $error) {
|
|
return $this->back(['iam_error' => $this->message($error)]);
|
|
}
|
|
}
|
|
|
|
public function callback()
|
|
{
|
|
try {
|
|
if ($this->browser() === '' || $this->request->get('error', '') !== '') {
|
|
throw new \RuntimeException('授权已取消或浏览器状态过期,请重新登录');
|
|
}
|
|
$ticket = (new IamLoginService())->callback($this->browser(), (string) $this->request->get('state', ''), (string) $this->request->get('code', ''));
|
|
return $this->back(['iam_ticket' => $ticket]);
|
|
} catch (\Throwable $error) {
|
|
return $this->back(['iam_error' => $this->message($error)]);
|
|
}
|
|
}
|
|
|
|
public function exchange()
|
|
{
|
|
if (!$this->request->isPost()) {
|
|
return $this->fail('请使用 POST 兑换登录状态')->code(405);
|
|
}
|
|
try {
|
|
if ($this->browser() === '') {
|
|
throw new \RuntimeException('浏览器登录状态已过期,请重新登录');
|
|
}
|
|
$payload = (new IamLoginService())->exchange($this->browser(), (string) $this->request->post('ticket', ''), (string) $this->request->header('origin', ''));
|
|
return $this->data($payload)->header($this->privateHeaders());
|
|
} catch (\Throwable $error) {
|
|
return $this->fail($this->message($error))->header($this->privateHeaders());
|
|
}
|
|
}
|
|
|
|
private function browser(): string
|
|
{
|
|
$value = (string) ($_COOKIE[self::COOKIE] ?? '');
|
|
return preg_match('/^[a-f0-9]{64}$/D', $value) ? $value : '';
|
|
}
|
|
|
|
private function back(array $query)
|
|
{
|
|
// Relative, fixed path: never derive the return origin from request headers or query strings.
|
|
return redirect('/admin/login?' . http_build_query($query, '', '&', PHP_QUERY_RFC3986))->header($this->privateHeaders());
|
|
}
|
|
|
|
private function privateHeaders(): array
|
|
{
|
|
return ['Cache-Control' => 'no-store', 'Referrer-Policy' => 'no-referrer'];
|
|
}
|
|
|
|
private function message(\Throwable $error): string
|
|
{
|
|
Log::warning('IAM login rejected: ' . get_class($error));
|
|
$message = $error->getMessage();
|
|
return preg_match('/^[\x{4e00}-\x{9fff}]/u', $message) ? $message : '统一账号登录失败,请重试或使用原账号登录';
|
|
}
|
|
}
|