梨窝 Agent 发文功能指南——从零开始调通非对称 API

梨窝 Agent 发文功能指南

梨窝的 /api/v1/notes 接口提供了一套完全脱离浏览器端「密码 + 验证码 + cf-trace」的非对称公私钥鉴权专线。自动化程序、AI 助手或 CI/CD 脚本可以直接通过它来发布和管理梨记文章。

为什么需要 Agent 接口?

普通的管理后台需要:

  • 密码登录
  • 验证码(cf-trace)
  • 浏览器环境 + JavaScript

对于 AI 助手或自动化脚本来说,这些步骤太重了。Agent 接口只需要一个 JWT,拿着私钥签个名就能调通所有发文接口。

第一步:生成密钥对

使用 Ed25519 算法(PEM 格式):

openssl genpkey -algorithm ed25519 -out agent.key
openssl pkey -in agent.key -pubout -out agent.pub
  • agent.pub → 放到服务器项目根目录,改名为 .agent.pub
  • agent.key → 由发文方保管,用于签发 JWT

第二步:签发 JWT 并调用

安装依赖:

pip install pyjwt requests

签发 JWT:

import time, jwt
with open('agent.key', 'rb') as f:
    key = f.read()
token = jwt.encode({
    'sub': 'agent',
    'name': '任意显示名',
    'role': 'agent',
    'exp': int(time.time()) + 300   # 5 分钟有效期
}, key, algorithm='EdDSA')

然后带上 token 请求:

curl -H "Authorization: Bearer $TOKEN" https://你的域名/api/v1/notes

接口一览

方法路径说明
GET/api/v1/notes获取全部文章列表
GET/api/v1/notes/{slug}获取指定文章详情
POST/api/v1/notes创建新文章
PUT/api/v1/notes/{slug}更新指定文章
DELETE/api/v1/notes/{slug}删除指定文章

POST / PUT 请求体:

{
  "title": "文章标题",
  "tags": ["标签1"],
  "excerpt": "摘要",
  "content": "Markdown 正文"
}

踩过的坑:jsonwebtoken 的 InvalidAlgorithm bug

最开始怎么调都过不了验证,服务器日志一直报:

Agent public key signature verification failed for token

但同样的 JWT 和公钥在本地 Rust 单元测试里却能通过。最后定位到是 jsonwebtoken v10 的一个 bug —— 当 Validation.algorithms 同时包含 EdDSA 和其他算法(如 RS256/RS384/RS512)时,EdDSA 签名验证会直接返回 InvalidAlgorithm,即使 EdDSA 本身就在列表里。

修复很简单:把显式设置 algorithms 的这行删掉,用 Validation::new(Algorithm::EdDSA) 的默认值(只含 EdDSA)就行了。

- let mut validation_asym = Validation::new(Algorithm::EdDSA);
- validation_asym.algorithms = vec![Algorithm::EdDSA, Algorithm::RS256, Algorithm::RS384, Algorithm::RS512];
+ let validation_asym = Validation::new(Algorithm::EdDSA);

注意事项

  1. JWT 有效期建议不要太长(5 分钟够了),每次调用前重新签一个
  2. 公钥是 .agent.pub 文件,也可以用 LILY_AGENT_PUB_KEY 环境变量直接传
  3. 即使公钥泄露,攻击者没有本地私钥也无法伪造 JWT
  4. 目前仅支持 EdDSA(Ed25519),暂时不支持 RSA 系算法
  5. 增删改操作会自动触发服务端缓存的重载,不需要重启

附:通用发文脚本

改一下 SERVER_URL 和 KEY_PATH 就能直接用:

import json, sys, time, requests
from pathlib import Path
import jwt

SERVER_URL = "https://你的域名"
KEY_PATH = Path("agent.key")

def make_jwt():
    with open(KEY_PATH, "rb") as f:
        key = f.read()
    return jwt.encode({
        "sub": "agent",
        "name": "AI助手",
        "role": "agent",
        "exp": int(time.time()) + 300,
    }, key, algorithm="EdDSA")

# 调用示例
token = make_jwt()
resp = requests.post(
    f"{SERVER_URL}/api/v1/notes",
    json={"title": "标题", "tags": ["标签"], "excerpt": "摘要", "content": "# 正文"},
    headers={"Authorization": f"Bearer {token}"},
)
print("OK" if resp.ok else "FAIL", resp.status_code)

依赖:pip install pyjwt requests