fischer-agentkit/docs/solutions/integration-issues/jwt-secret-dev-mode-user-id...

139 lines
7.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: "JWT secret 未设置导致 dev mode user_id 丢失 + reload 登录失效"
date: 2026-06-28
category: docs/solutions/integration-issues
module: server/auth, server/app, tools/calendar_tool, frontend/stores/auth
problem_type: integration_issue
component: authentication
symptoms:
- "agent 创建日历事件后回复'已完成',但日历 UI 中看不到事件"
- "浏览器 reload 后跳回登录页refresh token 未过期却验证失败"
- "calendar.db 中事件 user_id 为 default/zhangsanLLM hallucinateUI 查询用 user_id=None"
- "AuthMiddleware 处于 dev mode所有请求 current_user.user_id=None"
root_cause: config_error
resolution_type: config_change
severity: critical
tags: [jwt, auth, dev-mode, calendar, user-id, ephemeral-secret, login-state]
---
# JWT secret 未设置导致 dev mode user_id 丢失 + reload 登录失效
## Problem
用户报告4个问题(1) 启动后出现一堆测试对话且无法删除;(2) 要求创建下周一日历事件agent 回复已完成但日历里看不到;(3) 日历默认周日开始;(4) reload 后跳回登录页。深入调查发现问题2和4同根同源`AGENTKIT_JWT_SECRET` 从未设置,触发 dev mode 一系列连锁反应。
## Symptoms
- **日历事件数据不一致**calendar.db 中事件 `user_id="default"`/`"zhangsan"`LLM hallucinate`/calendar/events` 路由用 `user_id=None` 查询 → 返回空列表
- **reload 登录失效**:服务器重启后,之前签发的 refresh token 全部失效,前端 `startupCheck``whoami(refresh)` → 401 → `startupState='invalid'` → 跳 `/login`
- **AuthMiddleware dev mode**:所有请求 `current_user={"user_id": None, "username": "dev", "role": "admin"}`
- **CalendarTool schema 要求 LLM 提供 user_id**LLM 不知道真实值hallucinate 假值写入 DB
## What Didn't Work
- **前次"修复"日历 401**:在 AuthMiddleware dev mode 分支添加 synthetic dev user`user_id=None`),解决了 401 但引入了 `user_id=None` 数据不一致问题——治标不治本
- **前次"修复"登录状态**:在 auth.ts 注册4个 token provider但未解决 refresh token 跨重启失效的根本问题
- **假设 API 删除失败**curl 直接测试 DELETE 端点返回 200DB 行数减少——API 正常,问题是预存测试数据 + 前端视觉复活
## Solution
### 核心修复:设置持久化 JWT secret
`.env` 中添加(`.env` 已被 gitignore不会泄露
```bash
# 生成持久化 secret
python3 -c "import secrets; print(secrets.token_urlsafe(48))"
# 写入 .env
AGENTKIT_JWT_SECRET=<generated_secret>
```
这一步同时解决问题2AuthMiddleware 退出 dev mode`current_user.user_id` 从 JWT payload 获取真实值和问题4refresh token 跨重启持久化)。
### CalendarTool 注入真实 user_id
[calendar_tool.py](file:///Users/Chiguyong/Code/Fischer/fischer-agentkit/src/agentkit/tools/calendar_tool.py) 修改:
```python
# 之前schema 要求 LLM 提供 user_idLLM 会 hallucinate
"required": ["action", "user_id"],
# 之后:移除 user_id from required改用注入的 default_user_id
def __init__(self, calendar_service, default_user_id: str | None = None):
...
self._default_user_id = default_user_id
def _resolve_user_id(self, kwargs) -> str | None:
provided = kwargs.get("user_id")
if provided and isinstance(provided, str) and provided.strip():
return provided
return self._default_user_id
```
[app.py](file:///Users/Chiguyong/Code/Fischer/fischer-agentkit/src/agentkit/server/app.py) 在 lifespan 中查询 admin 用户并注入:
```python
# 查询第一个 active admin 用户作为 default_user_id
async with aiosqlite.connect(str(DEFAULT_AUTH_DB_PATH)) as db:
db.row_factory = aiosqlite.Row
cur = await db.execute(
"SELECT id FROM users WHERE is_active = 1 "
"ORDER BY CASE role WHEN 'admin' THEN 0 ELSE 1 END, created_at LIMIT 1"
)
row = await cur.fetchone()
if row is not None:
default_cal_user_id = str(row["id"])
calendar_tool = CalendarTool(
calendar_service=cal_service,
default_user_id=default_cal_user_id,
)
```
### CalendarGrid firstDay 配置
[CalendarGrid.vue](file:///Users/Chiguyong/Code/Fischer/fischer-agentkit/src/agentkit/server/frontend/src/components/calendar/CalendarGrid.vue) 添加 `firstDay: 1`
### 数据清理
```bash
# 清理测试对话
sqlite3 ~/.agentkit/conversations.db "DELETE FROM messages; DELETE FROM conversations;"
# 修复 calendar.db 中已存在的 hallucinate user_id
sqlite3 data/calendar.db "UPDATE calendar_events SET user_id = '<admin_user_id>' WHERE user_id IN ('default', 'zhangsan')"
```
## Why This Works
**根因因果链**
1. `AGENTKIT_JWT_SECRET` 未设置 → `get_jwt_secret()` 返回 None
2. [app.py:812-837](file:///Users/Chiguyong/Code/Fischer/fischer-agentkit/src/agentkit/server/app.py#L812-L837) 中 `explicit_jwt_secret or ""` 传给 AuthMiddleware → `jwt_secret=""``_is_dev_mode()=True`
3. dev mode 下 `get_or_create_jwt_secret()` 每次进程启动生成**新的 ephemeral secret**(不持久化)
4. **问题4**:服务器重启 → 新 ephemeral secret → 旧 refresh token 签名不匹配 → whoami 401 → 跳登录页
5. **问题2**dev mode 下 `current_user.user_id=None`CalendarTool 由 LLM hallucinate `user_id="default"`UI 路径用 `None` 查询不匹配 → 日历看不到事件
设置 `AGENTKIT_JWT_SECRET` 后:
- AuthMiddleware 收到真实 secret → `_is_dev_mode()=False` → JWT 验证启用 → `current_user.user_id` 从 payload 获取
- `get_jwt_secret()` 返回持久化 secret → 跨重启一致 → refresh token 持久有效
## Prevention
- **环境变量检查**:在 `app.py` 启动时检查 `AGENTKIT_JWT_SECRET` 是否设置,未设置时打印明显警告(当前 dev mode 日志不够醒目)
- **e2e 测试覆盖**:添加 e2e 测试覆盖以下场景:
1. 服务器重启后 reload 页面,验证登录状态保持
2. agent 创建日历事件后UI 能看到该事件(数据一致性 + `calendar_event_created` WS 消息到达前端,详见 [calendar-agent-create-no-refresh.md](file:///Users/Chiguyong/Code/Fischer/fischer-agentkit/docs/solutions/ui-bugs/calendar-agent-create-no-refresh.md)
3. 无效 token 返回 401非 dev mode 验证)
- **CalendarTool user_id 来源**:多用户场景需通过 agent 框架 contextvar 传递 per-request user_id当前 `default_user_id` 是 dev 模式单用户简化(代码中已标注 `ponytail:` 注释)
- **配置审计**`.env.example` 应列出 `AGENTKIT_JWT_SECRET` 并说明必须设置
## Related Issues
同一症状("agent 创建日历事件后 UI 看不到")现已记录**三个不同的根因**,调查时需同时排查:
- [日历能力缺失修复 + UI 布局优化](file:///Users/Chiguyong/Code/Fischer/fischer-agentkit/docs/solutions/logic-errors/calendar-capability-and-ui-fixes.md) — 前次日历问题CalendarTool 接入 ReAct本次是认证配置导致的 user_id 不匹配,同一领域不同根因
- [Calendar events created via agent chat do not refresh the calendar UI](file:///Users/Chiguyong/Code/Fischer/fischer-agentkit/docs/solutions/ui-bugs/calendar-agent-create-no-refresh.md) — `CalendarService.create_event` 创建成功后未广播 `calendar_event_created` WS 消息第三个根因2026-06-29 记录)
- [Portal 平台安全可靠性修复](file:///Users/Chiguyong/Code/Fischer/fischer-agentkit/docs/solutions/security-issues/portal-platform-security-reliability-fixes.md) — 认证相关修复