跳转至

API Key 授权配置

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

概念速览

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

每把 key 有五类约束:

  • scope —— 能调什么接口。qot:read / acc:read / trade:simulate / trade:real
  • allowed_markets / allowed_symbols / allowed_trd_sides —— 市场 / 品种 / 方向白名单
  • 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。

下一步