认证与安全: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小时吧?

请求签名与防重放攻击

这是最核心的部分。说白了,就是确保请求是合法的、没有被篡改的、而且只能被执行一次。

签名算法流程

  1. 客户端拼接请求参数(按字典序排序)
  2. 加上时间戳和随机数(Nonce)
  3. 用Secret Key做HMAC-SHA256签名
  4. 把签名放在请求头里发出去

服务端收到后,用同样的方式算一遍签名,比对是否一致。

// 签名生成示例(伪代码)
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,然后拼进待签名字符串里。

整体架构图

下面这张图展示了认证与安全的完整流程。从客户端发起请求,到服务端验签、鉴权、防重放,每一步都不能少。

认证与安全架构图 客户端 1. 请求+签名 API网关 2. 验签 认证服务 IP白名单校验 检查来源IP是否允许 JWT令牌校验 验证令牌有效性 防重放检测 Nonce + 时间戳校验 业务服务(交易/查询/风控)

这张图里,客户端先签名,API网关验签,然后依次过IP白名单、JWT校验、防重放检测。全部通过后,才放行到业务服务。每一步都像一道关卡,少一道都不行。

总结一下:API Key管身份,JWT管会话,IP白名单管来源,签名+防重放管请求合法性。四者配合,基本能挡住99%的攻击。剩下的1%?嗯,那是风控系统的事了,咱们下回再聊。

专注资料整理