跳转至

API Key 授权配置

从"本地开发无鉴权"升级到"生产可用的 scope + 限额 + 审计"。

概念速览

一把 key 一条记录,SHA-256 存在 keys.json(明文只在生成时打印,一次性)。

每把 key 有多类约束:

  • scope —— 能调什么接口。qot:read / acc:read / trade:simulate / trade:real / trade:unlock / admin
  • allowed_markets / allowed_symbols / allowed_trd_sides —— 市场 / 品种 / 方向白名单
  • allowed_acc_ids —— 账户白名单(v1.4.84+,per-key 限定只能动哪些 acc_id;见本文末尾)
  • max_order_value / max_daily_value —— 单笔和日累计金额上限
  • max_orders_per_minute —— 速率(滑动窗口)
  • hours_window —— 时间窗口(本地时区,支持跨午夜 22:00-04:00
  • allowed_machines —— 软机器绑定(防 keys.json 整体拷到别的机器)

REST / gRPC / 核心 WS / MCP 四条入口共享同一套 keys.json,你不用在四个地方各配一遍。

1. 安装 futucli

futu-opend 同一个 tarball / Cargo workspace 产出。

./futucli --help

2. 生成一把只读 key

./futucli gen-key --id research --scopes qot:read,acc:read

输出:

✅ Generated key "research"
   plaintext: fc_8a3f2b9c1e5d7a4b8c2f9e1d3a5b7c9f  ← 保存这串,别关终端
   stored in: /Users/you/.config/futu/keys.json

明文只打一次

keys.json 里只存 SHA-256。明文丢了就丢了,只能 revoke 重生成。

3. 生成一把带额度的交易 key

./futucli gen-key \
  --id sim-bot \
  --scopes qot:read,acc:read,trade:simulate \
  --allowed-markets HK,US \
  --allowed-trd-sides SELL \
  --max-order-value 100000 \
  --max-daily-value 500000 \
  --max-orders-per-minute 5 \
  --hours-window 09:30-16:00 \
  --expires 30d

这把 key 只能: - 模拟环境下下单(trade:simulate,不是 trade:real) - 只卖不买(--allowed-trd-sides SELLallowed_trd_sides 白名单) - 只做港美股 - 单笔 ≤ 10 万,日累计 ≤ 50 万 - 每分钟 ≤ 5 单 - 只在 09:30-16:00 之间能下 - 30 天后自动失效

4. 启动网关指向 keys.json

./futu-opend \
  --login-account 12345678 --login-pwd 'your_pwd' \
  --rest-port 22222 --rest-keys-file ~/.config/futu/keys.json \
  --grpc-port 33333 --grpc-keys-file ~/.config/futu/keys.json \
  --ws-keys-file ~/.config/futu/keys.json \
  --audit-log /var/log/futu-audit.jsonl

启动日志会变

INFO  keys_loaded=2 REST keys file loaded (Bearer auth enabled)
INFO  keys_loaded=2 gRPC keys file loaded (Bearer auth enabled)
INFO  keys_loaded=2 WS keys file loaded (Bearer/?token auth enabled)
INFO  audit JSONL logger enabled (target=futu_audit → file)

5. 用 key 访问各个接口

curl -H "Authorization: Bearer fc_8a3f..." \
     http://localhost:22222/api/accounts
grpcurl -H "authorization: Bearer fc_8a3f..." \
        -d '{"proto_id":1002}' \
        localhost:33333 futu.service.FutuOpenD/Request
# 浏览器 / 无 header 场景:?token=
websocat 'ws://localhost:44444/?token=fc_8a3f...'

# 原生客户端:Bearer header
websocat -H 'Authorization: Bearer fc_8a3f...' ws://localhost:44444/
# stdio
export FUTU_MCP_API_KEY="fc_8a3f..."
./futu-mcp --keys-file ~/.config/futu/keys.json

# HTTP:client 每次调用带 Authorization header(v1.1+)
./futu-mcp --keys-file ~/.config/futu/keys.json --http-listen 127.0.0.1:38765

6. 列 / 改 / 吊销 key

# 看现在有哪些
./futucli list-keys

# 吊销一把
./futucli revoke-key sim-bot

# 改机器绑定(就地编辑,不换 plaintext)
./futucli bind-key sim-bot --this-machine
./futucli bind-key sim-bot --replace --machines fp_abc123,fp_def456
./futucli bind-key sim-bot --freeze     # 临时禁用
./futucli bind-key sim-bot --clear      # 解除绑定

热重载

改完 keys.json 不用重启 —— kill -HUP <pid> 就行。REST / gRPC / 核心 WS / MCP 四条入口都支持 SIGHUP 热重载,新 key 秒级生效,吊销的 key 立刻失效。

7. 查审计日志

# 最近被拒的请求
jq 'select(.outcome=="reject")' /var/log/futu-audit.jsonl | tail -20

# 某把 key 今天下了多少单
jq 'select(.key_id=="sim-bot" and .endpoint|test("order"))' /var/log/futu-audit.jsonl | wc -l

# 按 reason 统计(DuckDB 也行)
jq -r 'select(.outcome=="reject") | .reason' /var/log/futu-audit.jsonl \
  | sort | uniq -c | sort -rn

8. Prometheus 抓 metrics

/metrics 端点在 bearer_auth middleware 之外,不需要 token:

curl http://localhost:22222/metrics
# futu_auth_events_total{iface="rest",outcome="allow",key_id="research"} 1234
# futu_auth_limit_rejects_total{iface="grpc",key_id="sim-bot",reason="rate"} 7
# futu_ws_filtered_pushes_total{required_scope="trade",key_id="research"} 42

生产用防火墙 / bind 127.0.0.1 限制外部访问 metrics。

9. 多 agent 账户隔离(v1.4.35+)

如果你跑多 agent / 多策略(比如用 LLM 把 N 个 bot 接 MCP),默认情况下每把带 trade:real scope 的 key 都能动所有已 unlock 的 acc_id。v1.4.35 加了 --allowed-acc-ids per-key 白名单:

# 生成 bot-A 的 key:只允许操作 10001 / 10002
./futucli gen-key --id bot-A \
  --scopes trade:real,acc:read \
  --allowed-acc-ids 10001,10002 \
  --max-order-value 5000 --max-daily-value 20000

# 生成 bot-B 的 key:只允许操作 10003
./futucli gen-key --id bot-B \
  --scopes trade:real,acc:read \
  --allowed-acc-ids 10003

bot-A 的 key 试图对 10003 下单 → daemon 立即返 acc_id 10003 not in allowed list {10001, 10002},不碰到后端。

4 层隔离强度对照

--allowed-acc-idsoperational safety不是 financial isolation。 两者的区别对选方案很关键:

方案 操作隔离 财务隔离 成本 场景
L1 单 daemon 全权限 0 单人自用
L2 单 daemon + 多 key + --allowed-acc-ids 纯现金下实质 ✅ LLM 多 agent / 多策略
L3 多 daemon + 同一 union card ✅(等同 L2) 同 L2 无额外隔离价值
L4 多 daemon + 多个 union card(多 login_id) ✅(后端 customer 级) 高(×N KYC) 大额融资 / 期权 / 企业

关键认知:L2 能做到什么 + 不能做到什么

后端风控是 customer 级。同 union card 下 Futu 后端(总权益 / BP / margin / 强平触发)按 customer(union card) 做,不是按 session —— 多 daemon 登同一个 login_id 在后端看来等同于"同一个客户开了几个会话"。

但"财务传导"的严重程度看账户类型

账户 / 策略类型 一个子账户亏光会怎样 是否传导
纯现金(不融资不融券) 该子账户余额归零、仓位清零;B 账户一分不动 不传导
融资账户但未借款 行为同纯现金 不传导
融资 + 实际借款且亏穿本金 A 净权益为负,broker 按统一账户可能跨账户强平 ⚠️ 有传导
期权组合 / 跨品种保证金 风控模型跨子账户聚合 ⚠️ 有传导

主流 LLM 多 agent 场景(纯现金 / 小额),L2 --allowed-acc-ids 实质上已经 等同财务隔离——没借钱就没传导路径。

L2 主要防: - Agent 代码 bug / LLM 幻觉 / prompt injection 让它误动不该动的账户 - Key 泄露后被拿去动白名单外账户 - 多 agent / 多策略共享 daemon 时按 key 做审计归因

L2 不防(这些情况要上 L4): - 主动开融资并借款后跨账户 margin call 传导 - 期权组合 / 期货保证金跨子账户聚合 - 极端行情(跳空 / 熔断)下风控强平失灵赤字

下一步