认证与安全:API Key管理、JWT令牌机制、IP白名单、请求签名与防重放攻击
做量化交易系统,我最怕什么?不是策略亏钱,而是API被人盗刷。
你想想看,你的交易接口暴露在公网上,每天成千上万的请求打过来。如果有人拿到了你的API Key,直接下单买一堆垃圾币,或者把你的账户资金转走……嗯,那画面太美我不敢看。
所以这一章,咱们聊聊认证与安全的四个核心模块。我做了这么多年交易系统,踩过的坑不少,今天把这些经验全倒出来。
API Key 管理:你的第一道防线
API Key说白了就是你的身份凭证。每个接入交易系统的客户端,都得分配一对Key:
- Access Key:相当于用户名,公开传输
- Secret Key:相当于密码,必须保密
我个人习惯把Secret Key设计成64位随机字符串,用HMAC-SHA256做签名。为什么?因为长度够长,暴力破解基本没戏。
核心原则:Secret Key永远不要明文存储。数据库里存的是哈希值,而且得加盐。
我在项目中遇到过一件事:有个同事把Secret Key写死在配置文件里,结果代码被上传到GitHub……嗯,那天我们花了整整一个下午轮换所有用户的Key。
API Key 的生命周期管理
| 阶段 | 操作 | 注意事项 |
|---|---|---|
| 创建 | 生成随机Key对,返回给用户 | Secret Key只显示一次,过后不可见 |
| 使用 | 客户端用Key签名请求 | 服务端验签,不传输Secret Key |
| 轮换 | 定期更换Key对 | 建议90天轮换一次 |
| 吊销 | 立即失效指定Key | 支持按需吊销,不用等过期 |
小技巧:我建议支持多Key并行。一个Key用于交易,一个用于查询。万一交易Key泄露,查询Key还能继续用,不至于全盘瘫痪。
JWT 令牌机制:无状态认证的利器
JWT(JSON Web Token)这东西,说白了就是一个自包含的令牌。它把用户信息、权限、过期时间全塞进一个字符串里,服务端不用查数据库就能验证身份。
一个标准的JWT长这样:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
分成三段:Header、Payload、Signature。中间用点隔开。
JWT 在交易系统中的使用场景
- 用户登录后:发放JWT,有效期通常设15-30分钟
- WebSocket连接:用JWT做初次握手认证
- 内部服务调用:服务间用JWT传递身份信息
注意:JWT的Payload是Base64编码,不是加密!别把敏感信息(比如Secret Key)放进去。我曾经见过有人把数据库密码写进JWT……嗯,那哥们后来被安全团队约谈了。
JWT 的刷新机制
Access Token有效期短,Refresh Token有效期长。Access Token过期了,用Refresh Token换新的。这样即使Access Token泄露,损失也有限。
// 刷新令牌的接口设计
POST /api/v1/auth/refresh
请求体: { "refresh_token": "xxx" }
响应: { "access_token": "新令牌", "expires_in": 900 }
IP 白名单:简单粗暴但有效
说实话,IP白名单这招有点老土,但真的管用。特别是对于量化交易这种场景——你的交易服务器IP通常是固定的。
我一般这样设计:
- 每个API Key可以绑定最多5个IP地址
- 支持IP段(CIDR格式),比如 192.168.1.0/24
- 请求来源IP不在白名单内,直接返回403
经验之谈:IP白名单不能单独用。为什么?因为IP可以伪造,而且云服务商的出口IP经常变。我建议IP白名单 + API Key双重验证。
另外,记得支持动态更新。用户换了服务器,总不能让人家等24小时吧?
请求签名与防重放攻击
这是最核心的部分。说白了,就是确保请求是合法的、没有被篡改的、而且只能被执行一次。
签名算法流程
- 客户端拼接请求参数(按字典序排序)
- 加上时间戳和随机数(Nonce)
- 用Secret Key做HMAC-SHA256签名
- 把签名放在请求头里发出去
服务端收到后,用同样的方式算一遍签名,比对是否一致。
// 签名生成示例(伪代码)
function generateSignature(params, secretKey, timestamp, nonce) {
// 1. 参数排序
let sortedKeys = Object.keys(params).sort();
let queryString = sortedKeys.map(k => `${k}=${params[k]}`).join('&');
// 2. 拼接待签名字符串
let payload = `${timestamp}\n${nonce}\n${queryString}`;
// 3. HMAC-SHA256签名
return HMAC_SHA256(payload, secretKey);
}
防重放攻击的关键
重放攻击是什么?就是攻击者截获你的请求,然后原封不动地再发一次。比如你下了一笔买入订单,攻击者截下来,重复发100次……
防重放靠两个东西:
- 时间戳:请求时间与服务器时间差不能超过5分钟
- Nonce(随机数):每个请求带一个唯一随机数,服务端记录已使用的Nonce,重复的直接拒绝
我曾经踩过的坑:Nonce用UUID生成,但没考虑分布式场景。结果两个服务同时处理请求,Nonce冲突了。后来我改用「时间戳+自增序列+机器ID」的方式生成Nonce,再也没出过问题。
完整的请求头设计
| 请求头 | 说明 | 示例 |
|---|---|---|
| X-API-Key | 用户的Access Key | abc123def456 |
| X-Timestamp | Unix时间戳(秒) | 1700000000 |
| X-Nonce | 唯一随机数 | 20240101-001-abc |
| X-Signature | 请求签名 | a1b2c3d4e5f6... |
建议:签名范围要包含请求体(Body)。有些系统只签查询参数,结果Body被篡改了都不知道。我习惯把整个请求体做一次SHA256,然后拼进待签名字符串里。
整体架构图
下面这张图展示了认证与安全的完整流程。从客户端发起请求,到服务端验签、鉴权、防重放,每一步都不能少。
这张图里,客户端先签名,API网关验签,然后依次过IP白名单、JWT校验、防重放检测。全部通过后,才放行到业务服务。每一步都像一道关卡,少一道都不行。
总结一下:API Key管身份,JWT管会话,IP白名单管来源,签名+防重放管请求合法性。四者配合,基本能挡住99%的攻击。剩下的1%?嗯,那是风控系统的事了,咱们下回再聊。