机器人密钥:为博客 AI 机器人设计的访问凭证
作者:leap
日期:2026-08-06
个人博客:https://vue2.xyz
前言
前阵子我的博客迎来了一批「新员工」——网盘上传、碎念记录、短链生成、文章发布,都是挂在 QQ 群里的 AI 机器人 skill。用得越多,越觉得当初那套「管理员账号密码换 JWT」的登录方式不对劲。这篇文档记录了我从账号密码登录的痛点出发,设计并实现「机器人专用 API 密钥」的完整过程,包括踩过的坑和几个关键设计决策。
一、需求背景
1.1 我的博客机器人
我的博客(Nuxt3 + FastAPI)跑了一批机器人 skill,通过 Hermes/Claude agent 挂在 QQ 群使用:
- pan:上传文件到网盘、生成分享链接和直链
- moment:发碎念、查列表、置顶
- shorturl:把长链接转短链
- article:发布/管理博客文章
每个 skill 由 env(配置)+ scripts/*.py(命令脚本)+ SKILL.md(给 agent 的行为说明)组成,脚本直接调博客的 FastAPI 接口。
1.2 认证需求
脚本要访问后台管理接口,必须有身份凭证。一开始的方案很简单:用管理员账号密码登录,换 JWT token,之后每个请求带 Authorization: Bearer <token>。
二、第一次尝试:账号密码登录(埋下隐患)
2.1 初步实现
脚本侧大概是这个套路:
# env 文件
BLOG_URL=https://vue2.xyz
BLOG_USERNAME=my_username
BLOG_PASSWORD=my_password
# 脚本里
def login():
url = f"{BLOG_URL}/api/auth/login"
data = json.dumps({"username": USERNAME, "password": PASSWORD}).encode()
...
return result["access_token"] # JWT,7 天有效
def _auth_headers():
return {"Authorization": f"Bearer {login()}"}
用起来确实简单:登录一次,缓存 7 天,脚本重启前不用再管。
2.2 遇到的问题
用着用着,问题逐渐暴露:
问题1:所有 skill 共享同一个管理员密码
- 每个 skill 的 env 里都明文存着管理员密码
- 改密码要同步改所有机器人的 env
- 任何一个 skill 的 env 泄露,等于把后台密码交出去
问题2:密码泄露没有隔离
- 密码一旦泄露,后台和所有机器人一起完蛋
- 想单独停掉某个机器人?做不到,只能改密码把所有人都踢下线
问题3:登录记录一团乱
- 脚本每次重启都要重新登录,后台登录记录里堆着一堆不明设备
- 分不清哪条是人登录的、哪条是机器人
2.3 失败总结
账号密码登录对「一个人类管理员」是合理的,但对「一群机器人」是灾难:身份无法隔离、权限无法细分、吊销只能一刀切。
三、创新思路:机器人专用 API 密钥
3.1 核心思想
既然密码登录的问题是「所有机器人共用一个身份」,那就给每个机器人发一把独立的密钥,用它代替账号密码。
设计目标:
- 每个机器人一把密钥,独立创建、独立吊销
- 脚本不再碰账号密码,env 只存密钥
- 密钥请求不占登录会话、不产生登录设备记录
- 和人类登录(密码+JWT)走两套通道,互不干扰
3.2 认证方式
密钥通过 X-API-Key 请求头发送:
X-API-Key: bk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
脚本侧从「账号密码换 token」简化成一行:
def _auth_headers() -> dict:
return {"X-API-Key": API_KEY}
四、技术实现
4.1 数据模型:bot_api_keys 表
后端加了一张 bot_api_keys 表,记录每把密钥的归属、名称和状态:
class BotApiKey(Base):
__tablename__ = "bot_api_keys"
id = Column(Integer, primary_key=True, index=True)
admin_id = Column(Integer, ForeignKey("admins.id", ondelete="CASCADE"), nullable=False, index=True)
name = Column(String(100), default="") # 机器人名字,如「网盘机器人」
api_key = Column(String(64), unique=True, nullable=False, index=True)
status = Column(String(20), default="active") # active / disabled
created_at = Column(DateTime, default=lambda: datetime.datetime.now(timezone.utc).replace(tzinfo=None))
updated_at = Column(DateTime, default=lambda: datetime.datetime.now(timezone.utc).replace(tzinfo=None), onupdate=lambda: datetime.datetime.now(timezone.utc).replace(tzinfo=None))
新表由 init_db() 的 create_all() 自动创建,无需手动迁移。
4.2 双通道鉴权:get_current_admin
关键的一步是改造认证依赖。后台所有管理接口都挂在 get_current_admin 上,我把它做成双通道:
def get_current_admin(request, credentials=Depends(security), db=Depends(get_db)):
# 1) 机器人密钥通道:X-API-Key
admin = _auth_admin_by_api_key(request, db)
if admin:
return admin
# 2) 原 JWT 通道(浏览器登录)
if credentials is None:
raise HTTPException(status_code=401, detail="Not authenticated")
...
带 X-API-Key 的请求,后端查 bot_api_keys 表验证密钥有效且启用后,直接当作「当前管理员」放行——和登录完全等价,能调所有后台接口。对接口层来说,完全不关心你是人还是机器人。
这是这个方案最优雅的地方:改动集中在认证依赖一处,所有现有接口零改动,机器人密钥自动获得全部权限。
4.3 密钥管理接口
后台配套了完整的密钥 CRUD:
GET /api/admin/bot-keys # 列表(脱敏显示)
POST /api/admin/bot-keys # 创建(完整密钥只返回一次)
POST /api/admin/bot-keys/{id}/toggle # 启用 / 禁用
DELETE /api/admin/bot-keys/{id} # 删除
创建时后端用 secrets.token_hex(24) 生成 51 位随机密钥,带 bk_ 前缀便于识别。完整密钥只在创建时显示一次,之后列表只显示脱敏值(bk_xxxxxxxx...xxxx),丢了只能删除重建。
五、配套:登录设备管理
5.1 JWT 会话追踪(jti)
顺手把后台登录也升级了。原来 JWT 是无状态的,payload 只有 {"sub": admin_id, "exp"},没法知道谁在登录、也没法踢人。现在 JWT 里加一个会话 ID(jti):
def create_token(admin_id, jti=None):
jti = jti or uuid.uuid4().hex
payload = {"sub": str(admin_id), "jti": jti, "exp": expire}
return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
登录时落一条 admin_sessions 记录,后台「站点设置 → 账号安全」能看到所有登录设备(设备名/IP/浏览器/系统/登录时间/最后活动),点一下就能踢出——踢出就是删掉会话记录,对应 token 立即失效。
5.2 一个微妙的取舍
这里有个关键设计决策:机器人的密钥认证不建会话、不占设备列表。它本来就不是「登录」,只是凭证。所以设备列表里看到的永远只有人登录的浏览器,干净、不混淆。
六、关键技术点
6.1 UA 解析识别设备
登录时用 user-agents 库解析 User-Agent,生成可读的设备名(电脑 · Windows · Chrome 之类)。Python urllib 的请求会被标成「🤖 机器人」,一眼能认出来:
def build_device_info(ua_str):
ua = parse(ua_str or "")
if ua_lower.startswith("python-urllib"):
device_name = "🤖 机器人"
elif ua.is_pc:
device_name = f"电脑 · {os} · {browser}"
...
6.2 僵尸会话清理
每次登录顺手清理超过 token 有效期(7 天)的会话记录,防止表无限膨胀,不需要定时任务。
6.3 最后活动时间节流
last_active_at 每 5 分钟才写一次库,避免每个请求都写库拖慢性能。
七、安全设计
几个关键决策:
- 独立吊销:每个机器人一把密钥,后台一键禁用/删除,只影响那一个,不牵连别人
- 密钥 = 管理员全部权限:单管理员系统没有细分角色,所以密钥的保管标准等同于后台密码,env 文件
chmod 600 - 只在创建时显示一次:完整密钥生成后不再回显,丢了就删除重建
- 登录设备可踢:发现可疑登录,后台直接踢出,token 立即失效
- 旧 token 兼容:改造前签发的无 jti 旧 token 直接放行,已登录用户完全不受影响,直到自然过期
八、Skill 使用示例
8.1 配置
每个 skill 的 env 简化为两行:
BLOG_URL=https://vue2.xyz
BLOG_API_KEY=bk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
8.2 脚本认证
def _auth_headers() -> dict:
return {"X-API-Key": API_KEY}
8.3 调用示例
# 网盘上传
result = _request("POST", f"{BLOG_URL}/api/pan/upload?...",
data=body, headers={**_auth_headers(), "Content-Type": "multipart/form-data; boundary=..."})
# 碎念列表
data = _request("GET", f"{BLOG_URL}/api/admin/moments", headers=_auth_headers())
8.4 后台操作
后台「授权管理 → 机器人密钥」:
+ 新建密钥→ 填名称(如「网盘机器人」)- 创建后立即复制完整密钥(只显示一次)
- 列表里随时可禁用/删除
九、优势对比
9.1 与账号密码登录对比
| 特性 | 账号密码 + JWT | 机器人密钥(X-API-Key) |
|---|---|---|
| 身份隔离 | ❌ 所有机器人共用一个密码 | ✅ 每机器人一把密钥 |
| 单独吊销 | ❌ 只能改密码全员下线 | ✅ 一键禁用/删除 |
| env 安全 | ❌ 明文存账号密码 | ✅ 只存密钥 |
| 登录记录 | ❌ 一堆不明设备 | ✅ 不占设备列表 |
| 泄露影响 | ❌ 后台全权暴露 | ✅ 只影响那一把密钥 |
| 脚本复杂度 | 需要登录+缓存 token | 一行请求头 |
9.2 与 JWT 长期 token 对比
| 特性 | 长期 JWT token | 机器人密钥 |
|---|---|---|
| 过期管理 | 需要续期/重新签发 | 无过期,手动吊销 |
| 会话占用 | 会建会话、占设备列表 | 不建会话 |
| 密钥可读性 | 解码可见内容 | 随机高熵,无可读信息 |
| 吊销粒度 | 全局 | 单把密钥 |
十、创新价值
10.1 技术价值
- 认证与授权分离:把「机器人的身份」从「人」里剥离,人用密码+会话,机器人用密钥+请求头
- 一处改造,全面生效:认证逻辑集中在
get_current_admin,现有接口零改动 - 可观测性:登录设备列表让后台登录状态一目了然,可远程踢出
10.2 工程价值
- 新 skill 零门槛:以后加机器人,后台点两下生成密钥,脚本里填上就行,不用再想认证
- 文档化:整套接口规格挂在后台「授权管理 → 机器人密钥 → 机器人 API 使用规格」,复制给任何 agent 都能照着写 skill
- 成本为零:复用已有博客系统,无新增依赖
十一、部署和维护
11.1 部署步骤
- 后端新增
bot_api_keys、admin_sessions两表,init_db()自动建表 - 认证改造集中在
auth.py,随后端一起部署 - 后台「授权管理」生成密钥,填进各 skill 的 env
11.2 维护要点
- 密钥泄露:后台删除重建,只影响对应机器人
- 机器人下线:禁用或删除密钥即可
- 登录设备可疑:站点设置 → 账号安全 → 踢出
十二、未来扩展
12.1 功能扩展
- 密钥备注/用途标记:更细的用途说明
- 请求日志:按密钥维度记录接口调用
- 细粒度权限:如果未来多管理员,可以给密钥分配部分接口权限
12.2 安全增强
- 密钥过期时间:支持设置密钥有效期,定期轮换
- 来源 IP 限制:密钥绑定服务器 IP
- 调用频率限制:防止密钥被滥用
十三、总结
13.1 探索历程
从账号密码登录,到发现问题(共享身份、无法隔离、无法吊销),再到设计机器人专用 API 密钥,本质上是把「机器人的身份」从「人」里剥离开的过程。
13.2 核心收获
- 双通道鉴权:一个依赖搞定人和机器人的身份,现有接口零改动
- 独立吊销:每个机器人一把密钥,安全边界清晰
- 配套可观测:登录设备列表让后台登录状态透明可控
13.3 展望
这套方案为博客的机器人生态打下了认证基础。以后无论是加新机器人、还是让第三方 agent 接入,都不需要再碰账号密码。回头再看,当初「用账号密码让机器人登录」的思路,就像把自家大门的钥匙配给每个机器人——现在换成每人一把独立的门卡,谁有问题刷掉谁,清爽多了。
文档版本:v1.0
最后更新:2026-08-06
作者:leap
个人博客:https://vue2.xyz
