跳转至

鉴权与限额

五类 Scope

Scope 能做什么
qot:read 行情只读 + 订阅
acc:read 账户只读(资金 / 持仓 / 订单 / 成交)
trade:simulate 模拟账户下单 / 改单 / 撤单
trade:real 真实账户下单 / 改单 / 撤单
trade:unlock MCP futu_unlock_trade 工具(从 OS keychain 读密码,不经 LLM)

keys.json 格式

{
  "version": 1,
  "keys": [
    {
      "id": "research",
      "hash": "sha256 hex",
      "scopes": ["qot:read", "acc:read"],
      "limits": {
        "allowed_markets": ["HK", "US"],
        "allowed_symbols": null,               // null = 不限
        "max_order_value": 100000.0,
        "max_daily_value": 500000.0,
        "hours_window": "09:30-16:00",
        "max_orders_per_minute": 5,
        "allowed_trd_sides": ["SELL"]
      },
      "allowed_machines": ["fp_abc123"],       // null = 不绑机器
      "created_at": "2026-04-15T10:00:00Z",
      "expires_at": "2026-05-15T10:00:00Z",    // null = 不过期
      "note": "research bot"
    }
  ]
}

字段详细见 keys.json 配置

五层限额

下单请求会依次过这几道闸门(任一拒就返回错):

1. 市场白名单   allowed_markets      —— ctx.market 在不在
2. 品种白名单   allowed_symbols      —— ctx.symbol 在不在
3. 方向白名单   allowed_trd_sides    —— BUY / SELL / SELL_SHORT / BUY_BACK
4. 时间窗口     hours_window         —— 本地时区,支持跨午夜 22:00-04:00
5. 单笔上限     max_order_value      —— qty × price
6. 速率         max_orders_per_minute —— 滑动窗口 60 秒
7. 日累计       max_daily_value      —— UTC 日滚

协调 auth 层和 handler 层

  • auth 层(middleware):市场 / symbol / side / value 都拿不到(body 没解析)。只跑 速率 + 时段 全局闸门。rate window commit timestamp
  • handler 层(业务路由):body 解析后有完整 ctx。跑 check_full_skip_rate 做剩下的检查,跳过 rate(已 commit,别重复计),但 daily 会累加

这样 rate 只计一次、daily 按金额如实累加。

软机器绑定

# 本机绑
./futucli gen-key --id bot --scopes trade:real --bind-this-machine

# 跨机器:
# 在目标机器跑
./futucli machine-id --for-key bot
# 出 fp_xxxxx

# 在签发机器
./futucli gen-key --id bot --scopes trade:real --bind-machines fp_xxxxx

指纹公式:SHA-256("futu-machine-bind:v1:" + key_id + ":" + raw_machine_id)

raw_machine_id 来源: - Linux:/etc/machine-id - macOS:IOPlatformUUID(ioreg) - Windows:尚未实现

软绑定的强度:挡不住登上目标机器的攻击者(攻击者能读 machine-id 伪造指纹);能挡 keys.json 被整体拷到其他机器的意外泄漏。生产级强绑定应走 TPM / Secure Enclave(未来版本)。

SIGHUP 热重载

kill -HUP <pid> 重新读 keys.json。四条入口(REST / gRPC / 核心 WS / MCP)都支持:

  • 新加的 key 秒级可用
  • 改 scope / 限额 / expires_at / allowed_machines 立刻生效(不用重启)
  • remove-key 吊销的 key 下次请求被拒(不等超时)

实现:KeyStoreArcSwap<KeysFile>,SIGHUP → 重读文件 → 原子替换 snapshot。请求路径按 get_by_id(key_id) 取当前 snapshot。

审计

每一次 allow / reject / trade 事件都走 futu_auth::audit

WARN target=futu_audit iface=rest endpoint=/api/order
     key_id=bot_a outcome=reject reason="limit: rate limit exceeded: ..."

--audit-log <PATH>target=futu_audit 的事件单独写 JSONL 文件,jq / DuckDB 后处理:

jq 'select(.outcome=="reject" and .iface=="grpc")' /var/log/futu-audit.jsonl

面向攻击者的 fail-closed 设计

场景 行为
/api/unknown-path 404(不漏接口是否存在,不误导"换 key 就能过")
tools/call 未登记工具 reject "unknown MCP tool"
gRPC 未覆盖 proto_id 默认 trade:real scope(保守)
per-call api_key 无效 (不回落 startup key,防升级攻击)
握手后 key 被 revoke 下次请求 reject "key revoked"