机器人密钥:为博客AI机器人设计的访问凭证

2026年8月6日 兴趣使然 16 分钟阅读 4 次阅读
📖 文章摘要

针对多只博客QQ机器人共享账号密码的认证痛点,设计机器人专用API密钥方案:X-API-Key 请求头认证、双通道鉴权、独立吊销、不占登录会话,附完整实现与配套登录设备管理。

机器人密钥:为博客 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 后台操作

后台「授权管理 → 机器人密钥」:

  1. + 新建密钥 → 填名称(如「网盘机器人」)
  2. 创建后立即复制完整密钥(只显示一次)
  3. 列表里随时可禁用/删除

九、优势对比

9.1 与账号密码登录对比

特性 账号密码 + JWT 机器人密钥(X-API-Key)
身份隔离 ❌ 所有机器人共用一个密码 ✅ 每机器人一把密钥
单独吊销 ❌ 只能改密码全员下线 ✅ 一键禁用/删除
env 安全 ❌ 明文存账号密码 ✅ 只存密钥
登录记录 ❌ 一堆不明设备 ✅ 不占设备列表
泄露影响 ❌ 后台全权暴露 ✅ 只影响那一把密钥
脚本复杂度 需要登录+缓存 token 一行请求头

9.2 与 JWT 长期 token 对比

特性 长期 JWT token 机器人密钥
过期管理 需要续期/重新签发 无过期,手动吊销
会话占用 会建会话、占设备列表 不建会话
密钥可读性 解码可见内容 随机高熵,无可读信息
吊销粒度 全局 单把密钥

十、创新价值

10.1 技术价值

  1. 认证与授权分离:把「机器人的身份」从「人」里剥离,人用密码+会话,机器人用密钥+请求头
  2. 一处改造,全面生效:认证逻辑集中在 get_current_admin,现有接口零改动
  3. 可观测性:登录设备列表让后台登录状态一目了然,可远程踢出

10.2 工程价值

  1. 新 skill 零门槛:以后加机器人,后台点两下生成密钥,脚本里填上就行,不用再想认证
  2. 文档化:整套接口规格挂在后台「授权管理 → 机器人密钥 → 机器人 API 使用规格」,复制给任何 agent 都能照着写 skill
  3. 成本为零:复用已有博客系统,无新增依赖

十一、部署和维护

11.1 部署步骤

  1. 后端新增 bot_api_keysadmin_sessions 两表,init_db() 自动建表
  2. 认证改造集中在 auth.py,随后端一起部署
  3. 后台「授权管理」生成密钥,填进各 skill 的 env

11.2 维护要点

  • 密钥泄露:后台删除重建,只影响对应机器人
  • 机器人下线:禁用或删除密钥即可
  • 登录设备可疑:站点设置 → 账号安全 → 踢出

十二、未来扩展

12.1 功能扩展

  1. 密钥备注/用途标记:更细的用途说明
  2. 请求日志:按密钥维度记录接口调用
  3. 细粒度权限:如果未来多管理员,可以给密钥分配部分接口权限

12.2 安全增强

  1. 密钥过期时间:支持设置密钥有效期,定期轮换
  2. 来源 IP 限制:密钥绑定服务器 IP
  3. 调用频率限制:防止密钥被滥用

十三、总结

13.1 探索历程

从账号密码登录,到发现问题(共享身份、无法隔离、无法吊销),再到设计机器人专用 API 密钥,本质上是把「机器人的身份」从「人」里剥离开的过程。

13.2 核心收获

  • 双通道鉴权:一个依赖搞定人和机器人的身份,现有接口零改动
  • 独立吊销:每个机器人一把密钥,安全边界清晰
  • 配套可观测:登录设备列表让后台登录状态透明可控

13.3 展望

这套方案为博客的机器人生态打下了认证基础。以后无论是加新机器人、还是让第三方 agent 接入,都不需要再碰账号密码。回头再看,当初「用账号密码让机器人登录」的思路,就像把自家大门的钥匙配给每个机器人——现在换成每人一把独立的门卡,谁有问题刷掉谁,清爽多了。


文档版本:v1.0
最后更新:2026-08-06
作者:leap
个人博客https://vue2.xyz

文章创建于:2026年8月6日CC BY-NC-SA 4.0
📡

评论

暂无评论,来写第一条吧