# 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://` 引用), 不要写进仓库或聊天记录。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 命令接入 - Telegram:`TELEGRAM_COMMANDS` 增加 `login`,并复用 PersonalHub 现成的 `configure_handlers(login_handler=...)` 形状;文案可沿用 「已生成登录链接,5 分钟内有效,请勿转发」。 - QQ(LionQQBot):增加 `/login`(别名 `/登录`),走同一套生成逻辑。 - 按项目既有约定,新命令必须同时覆盖 QQ 与 Telegram,帮助菜单同步更新。 ### 2.3 与 PersonalHub 现有 /login 的关系 PersonalHub 已有一个 `/login`,但它签发的是**它自己面板**的票据 (`OneTimePanelLoginTickets` + `/panel/telegram-login`),与 LionWebsite 无关。 接入时二选一,建议前者: 1. `/login` 仍只回 PersonalHub 面板链接,另加 `lionlogin`(或 `/面板`)专给 LionWebsite —— 语义清晰,不改动既有命令行为; 2. 让 `/login` 一条消息里同时给出两个链接 —— 少一个命令,但会改动现有文案与测试。 ## 3. 可选收尾(未做,需要时另开任务) - 移动端 `PrivateMainForMobile` 仍写死 `authCode: "alone"`。后端两种方式都接受, 所以它能用;要一并切到会话,照搬 `PrivateMain/src/store/index.js` 的改法即可。 - `sourcecode/storageNode` 有两处硬编码 `alone`(`CustomUtil.java` 的 `/message2me` 与 `MultiThreadedHTTPServer.java` 的本机鉴权),属另一仓库,需单独发布后才能退役字面量。 - 概览页的「本机订阅」链接 `https://personal.lionwebsite.xyz/sub/self` 目前是 404, 后端没有对应映射,属历史遗留,与本次登录改造无关。