9c25b3a9e5
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
3.7 KiB
3.7 KiB
记住我(30 天会话)设计
日期:2026-08-07 状态:已批准
背景
WebUI 登录当前使用无状态 HMAC 签名会话 Cookie(evoscientist_webui_session),有效期由 WEBUI_AUTH_SESSION_TTL_HOURS 控制(默认 12 小时)。用户希望登录页提供"记住我"选项,勾选后登录状态保持 30 天。
需求
- 登录页新增"Remember me for 30 days"复选框,默认不勾选。
- 勾选:会话有效 30 天,关闭浏览器再打开无需重新登录。
- 未勾选:会话级 Cookie(浏览器关闭即失效),服务端
expiresAt保留 12 小时兜底。 - 认证未启用(
WEBUI_AUTH_ENABLED != "true")时行为完全不变。 - 旧会话 Cookie 无需迁移,自然过期。
方案
扩展现有签名会话 Cookie,按勾选状态使用不同 TTL。不引入新存储、不新增 Cookie。
1. 配置层 — src/lib/auth.ts
新增:
const DEFAULT_REMEMBER_TTL_DAYS = 30;
export function rememberTtlSeconds(): number {
const days = Number(process.env.WEBUI_AUTH_REMEMBER_TTL_DAYS);
if (!Number.isFinite(days) || days <= 0 || days > 90) {
return DEFAULT_REMEMBER_TTL_DAYS * 24 * 60 * 60;
}
return Math.floor(days * 24 * 60 * 60);
}
与现有 sessionTtlSeconds() 模式一致:环境变量 WEBUI_AUTH_REMEMBER_TTL_DAYS,默认 30,上限 90,非法值回退默认。
2. 会话签发 — src/lib/server/auth.ts
createSession(username, role, ttlSeconds: number):增加 TTL 参数,用于计算 payload 的expiresAt(由调用方传入rememberTtlSeconds()或sessionTtlSeconds())。authCookieOptions(ttlSeconds: number | null):参数为null时不设置maxAge(会话级 Cookie);否则maxAge取该值。其余选项(httpOnly、sameSite=lax、secure、path)不变。SessionPayload结构不变;decodeSessionPayload不变。- proxy.ts 中间件校验逻辑不变(已基于 payload 的
expiresAt判断,与 CookiemaxAge独立)。
3. 登录路由 — src/app/api/auth/login/route.ts
- 请求体新增可选字段
rememberMe,仅严格true视为勾选,其余(缺失、false、非布尔)视为未勾选。 - 勾选:
createSession(user.username, user.role, rememberTtlSeconds())+authCookieOptions(rememberTtlSeconds())。 - 未勾选:
createSession(user.username, user.role, sessionTtlSeconds())+authCookieOptions(null)(会话级 Cookie,expiresAt为 12 小时兜底)。 - 其余校验(origin、用户名/密码长度、错误响应)不变。
4. 登录页 — src/app/login/page.tsx
- 密码框下方新增复选框,文案固定为 "Remember me for 30 days"(写死,不从配置读取)。
- 状态
rememberMe,默认false,提交时加入请求体。 - 复选框在
submitting时禁用。 - 样式沿用现有 Tailwind 类,与表单其余部分一致。
5. 错误处理与边界
- 认证未启用:登录路由已返回 404,不涉及本功能。
- 勾选但环境变量非法:回退 30 天,不报错。
- 登出逻辑不变(清除 Cookie)。
- 修改密码轮换密钥后所有会话(含 30 天会话)失效,行为与现状一致。
测试
src/lib/auth单测(新增或扩展):rememberTtlSeconds()的默认值、合法值、非法值(非数字、负数)、超上限(>90 天)回退。- 登录路由测试:
rememberMe: true→ Set-Cookie 含Max-Age≈ 30 天,payloadexpiresAt≈ 30 天后。rememberMe: false或缺失 → Set-Cookie 不含Max-Age,payloadexpiresAt≈ 12 小时后。rememberMe为非布尔值 → 按未勾选处理。
- 手动验证:勾选登录后重启浏览器仍保持登录;未勾选关闭浏览器后需重新登录。
非目标(YAGNI)
- 不做 remember token 轮换 / 服务端可撤销的 refresh token 存储。
- 不做设备管理、踢出会话界面。
- 不做"30 天内活跃自动续期"。