# LionWebsite 一键登录(/login):实现说明与机器人侧待办 状态:**主站侧已实施上线**(提交 `00e2715`);机器人侧(QQ / Telegram)**待实施**。 关联待办:`todos/open-td-20260921-a1b2c3-lionwebsite-login-tg.md`。 用户已明确选择**接法 B:共享密钥自签票据**,理由是机器人到主站的网络调用不可靠。 因此主站**不提供**任何票据签发端点,也不需要机器人侧发起 HTTP 请求——机器人本地 用共享密钥算出票据即可。 ## 1. 已经上线的东西(主站侧) ### 1.1 票据格式 ``` v1.<签发时间的 Unix 秒级时间戳>. ``` 签名原文为 `v1.<时间戳>`(固定前缀 `v1.` 加时间戳本身),密钥为共享密钥的 UTF-8 字节。 等价于 `hex(HMAC_SHA256(key, "v1." + timestamp))`。 ### 1.2 校验规则 - 必须恰好三段,第一段必须是 `v1`; - 签名比较用常量时间(`MessageDigest.isEqual`); - 时间戳只接受 `[now - 300s, now]`,**未来时间戳一律拒绝**(防伪造者用远期时间换长期有效); - 窗口内**允许重放**(用户明确接受),过期即失效; - 默认 300 秒有效期,可用 `personal.login.ticket-ttl-seconds` 调整,下限 30 秒。 ### 1.3 端点 | 端点 | 作用 | | --- | --- | | `GET /login?t=<票据>` | 校验票据 → 作废旧会话(防会话固定)→ 建新会话 → 302 跳 `/index`;失败 302 跳 `/denied` | | `GET /login/logout` | 销毁会话 → 302 跳 `/denied` | | `GET /denied` | 静态提示页:「请在机器人里发送 `/login`」 | nginx 无需改动:`location /` 会把 `/login` 改写成后端 `/personal/login`。 ### 1.4 会话与鉴权 - 登录成功下发 `JSESSIONID`,参数为 `Path=/`、`HttpOnly`、`SameSite=Lax`,14 天滑动过期。 **`Path=/` 是必须的**:nginx 会把 `/user` 改写成后端 `/personal/user`,若沿用容器按 请求路径推导的 `/personal`,浏览器判定 `/user` 不匹配就不会带会话,面板会一直 401。 - `PersonalInterceptor` 放行「有效会话 **或** `AuthCode=alone`」,两者都拒绝时返回 **401**。 `alone` 是留给下载器前端与存储节点 `/message2me` 推送的,本次**没有**退役它。 ### 1.5 密钥 - 主站从环境变量 `PERSONAL_LOGIN_SECRET` 读取(`personal.login.secret`), 由 systemd drop-in `/etc/systemd/system/lionwebsite.service.d/login-secret.conf` 加载 `/etc/lionwebsite/login-secret.env`(`600` 权限,不在仓库里)。 - 密钥为空时**一律拒绝**票据,不会退化成放行。 - 轮换方式:改两侧配置并重启。轮换后旧票据在 300 秒内自然失效。 ## 2. 机器人侧待实施(host-vm103-debian-qq) ### 2.1 生成票据 密钥必须与主站一致。**已由主人批准写入 PersonalHub 保险库**,引用为 `secret://services/service-lionwebsite/panel-login-secret`;机器人侧按既有秘密读取流程 申请取值,不要写进仓库或聊天记录。Python 侧计算方式: ```python import hashlib, hmac, time def lionwebsite_login_link(secret: str, base: str = "https://personal.lionwebsite.xyz") -> str: stamp = str(int(time.time())) signed = f"v1.{stamp}" digest = hmac.new(secret.encode(), signed.encode(), hashlib.sha256).hexdigest() return f"{base}/login?t=v1.{stamp}.{digest}" ``` Shell 等价写法(`openssl`)供人工验证用: ```sh SIG=$(printf 'v1.%s' "$STAMP" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}') ``` ### 2.2 命令接入(主人已确认的方案) **复用既有 `/login`,不新建命令名;一条回复里同时给出两个登录链接。** - Telegram:`telegram_panel_login()` 的返回文案改为同时给出 PersonalHub 面板链接与 LionWebsite 面板链接,`TELEGRAM_COMMANDS` 里 `login` 的描述同步更新。 - QQ(LionQQBot):既有 `/login`(别名 `/登录`)返回同样两条链接,共用同一套生成逻辑。 - PersonalHub 未完整配置(缺 password / session_secret / TOTP)时,现有逻辑会回一句 配置提示;此时**仍要发出 LionWebsite 链接**,不要让一条链接的失败带掉另一条。 - 帮助菜单(Telegram `/help` 与 QQ 菜单)中 `/login` 的描述同步更新。 - 文案不要写成「两条都只能用一次」:PersonalHub 链接是一次性的, LionWebsite 票据是 300 秒窗口内可重放。 示例文案: ``` 已生成登录链接: · Personal Hub:[打开面板](< PersonalHub 链接 >) · LionWebsite:[打开面板](< LionWebsite 链接 >) 两条链接 5 分钟内有效,请勿转发。 ``` ### 2.3 与 PersonalHub 现有 /login 的关系 PersonalHub 的 `/login` 原本只签发**它自己面板**的票据 (`OneTimePanelLoginTickets` + `/panel/telegram-login`)。改造时保留这条, 再在同一回复里附加 LionWebsite 链接;两者的票据机制与有效期互不影响。 ## 3. 可选收尾(未做,需要时另开任务) - 移动端 `PrivateMainForMobile` 仍写死 `authCode: "alone"`。后端两种方式都接受, 所以它能用;要一并切到会话,照搬 `PrivateMain/src/store/index.js` 的改法即可。 - `sourcecode/storageNode` 有两处硬编码 `alone`(`CustomUtil.java` 的 `/message2me` 与 `MultiThreadedHTTPServer.java` 的本机鉴权),属另一仓库,需单独发布后才能退役字面量。 - 概览页的「本机订阅」链接 `https://personal.lionwebsite.xyz/sub/self` 目前是 404, 后端没有对应映射,属历史遗留,与本次登录改造无关。