fix(auth): 修复登录后反复报未配置账号并跳回登录页的死循环
根因:ny_save_config 只写不校验,Db::settingsSet 内部吞异常且返回 void。 data/ 目录不可写时登录页照常显示登录成功,但下一请求读到的账号仍是空的 → 抛未配置卷王登录账号 → 前端 auth 打标 → 跳登录页 → 无限循环。 修复: - Db::settingsSet 改返回 bool 并回读校验;新增 settingsDelete - ny_save_config 双写后均做回读校验,失败写入 ny_config_save_error() - 新增 ny_config_diag()/ny_session_usable(),暴露来源/可写性/是否已存账号 - ny_load_config 把 null 账号强制成空字符串(脏数据不再伪装成未配置) - login.php 存凭据后回读确认,存不住/会话不可写直接报错而不是假成功 - login.php 账号为空时页面直接给出 data 目录写权限提示 - config_save 合并逻辑抽成 ny_apply_config_input:账号/密码不许被改空 - 鉴权失败响应带 extra.diag,前端 401 也算 auth - 前端防死循环:刚登录 120s 内或已跳 2 次则停止自动跳转,弹窗给出真实原因+诊断 测试:新增 tests/test-config-persist.php(29) 与 tools/test-login-loop.js(19); run-all.sh 纳入两者。全套 148+48=196 项通过。
This commit is contained in:
@@ -10,13 +10,17 @@ require __DIR__ . '/bootstrap.php';
|
||||
// 先加载配置:下面已登录分支要用 $cfg 构造 SurveyKingClient 探活,必须在前面就准备好
|
||||
$cfg = ny_load_config();
|
||||
|
||||
$notice = ''; // 非阻断提示(例如"上次会话已失效,请重新登录")
|
||||
|
||||
if (ny_is_auth()) {
|
||||
// 已登录但卷王 token 可能已过期:用当前 token 探一次卷王 /system,能 200 即视为有效,否则清掉本地会话强制重新登录
|
||||
// 已登录但卷王 token 可能已过期:用当前 token 探一次卷王,能通即视为有效,否则清掉本地会话强制重新登录
|
||||
try {
|
||||
$client = new SurveyKingClient($cfg);
|
||||
$client->ping();
|
||||
} catch (Throwable $e) {
|
||||
// 卷王 token 已失效或配置错误 → 清会话 + 强制显示登录页(避免 login.php 把人踢回 index.php 形成死循环)
|
||||
// 把失败原因带出来,避免用户只看到登录框却不知道为什么被踢回来
|
||||
$notice = '已退出之前的登录(' . $e->getMessage() . ')';
|
||||
$_SESSION = [];
|
||||
@session_destroy();
|
||||
}
|
||||
@@ -43,13 +47,36 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
try {
|
||||
$client = new SurveyKingClient($test);
|
||||
$client->getToken(true);
|
||||
|
||||
// 登录成功:把账号与密码写回配置(本地单用户工具,持久化密码以便 token
|
||||
// 失效时平台能自动重新登录卷王,避免后台刷新陷入"未配置账号"的死状态)
|
||||
$cfg['username'] = $user;
|
||||
$cfg['password'] = $pwd;
|
||||
ny_save_config($cfg);
|
||||
ny_login();
|
||||
$ok = true;
|
||||
$saved = ny_save_config($cfg);
|
||||
|
||||
// 关键:必须回读确认真的存住了。以前这里不看返回值,data 目录不可写时
|
||||
// 会显示"登录成功",但下一个请求读到的账号仍是空的 → 无限跳回登录页。
|
||||
$back = ny_load_config();
|
||||
$persistOk = $saved
|
||||
&& trim((string)($back['username'] ?? '')) === $user
|
||||
&& (string)($back['password'] ?? '') === $pwd;
|
||||
|
||||
if (!$persistOk) {
|
||||
$d = ny_config_diag();
|
||||
$err = "登录卷王成功,但凭据没能保存到本机配置,继续进入会立刻又提示“未配置账号”。\n"
|
||||
. "请检查 data 目录写权限后重试:\n"
|
||||
. "· 配置来源:" . $d['storage'] . "(可写=" . ($d['cfg_writable'] ? '是' : '否') . ")\n"
|
||||
. "· 数据目录:" . $d['data_dir'] . "(可写=" . ($d['dir_writable'] ? '是' : '否') . ")\n"
|
||||
. "· 数据库:" . ($d['db_connected'] ? '已连接(可写=' . ($d['db_writable'] ? '是' : '否') . ')' : '未连接 ' . $d['db_error']) . "\n"
|
||||
. "· 保存错误:" . ($d['save_error'] !== '' ? $d['save_error'] : '(无)');
|
||||
} elseif (!ny_session_usable($sessWhy)) {
|
||||
// 会话存不住时同样会"登录成功→立刻被踢回登录页",必须在这一步拦住
|
||||
$err = "凭据已保存,但 PHP 会话不可用,登录状态无法保持:\n" . $sessWhy
|
||||
. "\n请在 php.ini 中把 session.save_path 指向一个可写目录后重试。";
|
||||
} else {
|
||||
ny_login();
|
||||
$ok = true;
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
$err = '登录失败:' . $e->getMessage();
|
||||
}
|
||||
@@ -75,9 +102,16 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
</div>
|
||||
</div>
|
||||
<p class="login-tip">系统连接信息已配置(<?= htmlspecialchars(parse_url($cfg['base_url'], PHP_URL_HOST)) ?>/<?= htmlspecialchars(trim($cfg['api_prefix'], '/')) ?>)。请输入账号密码完成登录。</p>
|
||||
<?php if (trim((string)$cfg['username']) === ''): ?>
|
||||
<div class="alert" style="white-space:pre-wrap">⚠️ 本机尚未保存卷王账号(配置来源:<?= htmlspecialchars(ny_config_source()) ?>)。
|
||||
如果每次登录成功都回到本页,多半是 data 目录不可写导致账号没存住——
|
||||
请让 data/ 目录(<?= htmlspecialchars(NY_DATA) ?>)对 PHP 进程可写后重试。</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($err !== ''): ?>
|
||||
<div class="alert" style="white-space:pre-wrap"><?= htmlspecialchars($err) ?></div>
|
||||
<?php elseif ($notice !== ''): ?>
|
||||
<div class="alert" style="white-space:pre-wrap"><?= htmlspecialchars($notice) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="post">
|
||||
@@ -96,7 +130,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
<div class="login-brand" style="justify-content:center"><span class="logo">NY</span>
|
||||
<div class="brand-text"><strong>登录成功</strong><small>正在进入数据平台…</small></div>
|
||||
</div>
|
||||
<script>setTimeout(function(){ location.href='index.php'; }, 600);</script>
|
||||
<script>
|
||||
// 记录"本次登录成功"的时间戳:前端据此判断"刚登录完又报鉴权失败",
|
||||
// 这种情况不再自动跳回登录页(否则会形成死循环),而是把真实错误显示出来。
|
||||
try { sessionStorage.setItem('ny_login_at', String(Date.now())); sessionStorage.removeItem('ny_auth_jumps'); } catch (e) {}
|
||||
setTimeout(function(){ location.href='index.php'; }, 600);
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
Reference in New Issue
Block a user