4. 令牌生成接口规范

这一章我们来聊令牌生成接口。这是令牌化服务的核心入口,所有的数据保护都从这里开始。我在项目里见过不少设计,有的把生成参数堆得跟超市货架一样,有的又精简到连调用方该传什么都不清楚。

咱们今天就把请求格式、响应字段、参数说明与约束一次理清。你照着这个写,基本不会踩坑。

4.1 请求设计原则

我需要你记住三个字:少而精。

令牌生成接口不是万能工具箱。它只负责一件事:输入敏感数据,输出令牌和必要的元数据。别的逻辑别往里塞。

核心约束:

  • 严禁把令牌映射表直接暴露给调用方
  • 每次请求必须携带幂等键(Idempotency-Key),防止重复生成
  • 请求体使用 JSON,字符编码统一 UTF-8

4.2 请求格式规范

我之前接手的某支付项目,客户端传了个 Base64 编码的 XML 报文进来,解析直接挂了。你说头疼不头疼。所以咱们强制统一。

元素 说明 约束
method 请求方法 仅支持 POST
path 端点路径 /api/v1/tokens
content-type 媒体类型 application/json; charset=utf-8
idempotency-key 幂等键(Header) UUID v4,必填
request-id 跟踪 ID(Header) 必填,用于链路追踪

你可能会问,为什么幂等键不放在地址栏?嗯,我当时也这么想过,直到我深究了 HTTP 语义。幂等性和请求方法是绑定的,放在 Header 里才是正路。

请求体结构示例:

{
  "plaintext": "622202******1234",
  "context": {
    "domain": "payment",
    "user_id": "u_abc123",
    "expiration": "2026-12-31T23:59:59Z"
  },
  "options": {
    "_type": "PAN",
    "lifetime_seconds": 3600,
    "reversible": false
  }
}

4.3 参数说明与约束

每个字段都有它的脾气。下面这张表我建议你存一份,联调时对着看。

字段名 类型 必填 约束说明
plaintext string 是 原始敏感数据,长度 ≤ 1024 字符,不可包含不可见字符
context.domain string 是 业务域标识,枚举值:payment, identity, compliance
context.user_id string 否 关联用户,若提供则支持后续的令牌回收
context.expiration string 否 ISO 8601 格式,若不传则使用系统默认有效期
options._type string 是 PAN / UUID / RANDOM 枚举之一
options.lifetime_seconds int 否 存活秒数,最小值 300,最大值 86400
options.reversible bool 否 是否可逆,false 表示仅支持单向映射

注意:plaintext 绝不能直接在日志中输出。我曾见过团队把整个 request body 打到 access log 里,后果就是信用卡号明文落库。审计查出来直接红牌罚款。

4.4 响应字段详解

响应体我习惯分成三块:令牌本体、元数据、签名。这么设计调用方一眼就能找到自己想要的。

正常响应示例(HTTP 201):

{
  "_id": "tok_eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjYw...",
  "": "4389-****-****-1234",
  "metadata": {
    "_type": "PAN",
    "issued_at": "2024-09-15T10:30:00Z",
    "expires_at": "2024-09-15T11:30:00Z",
    "domain": "payment",
    "user_id": "u_abc123"
  },
  "signature": "a1b2c3d4e5f6..."
}
字段 说明 是否必须
_id 令牌唯一标识,用于后续查询和撤销 是
脱敏后的令牌值。可展示给终端用户 是
metadata 令牌的上下文信息 是
signature 对 _id + 的 HMAC 签名 强烈建议

我的习惯:signature 我用 HMAC-SHA256,密钥只存在令牌服务的机密存储里。这样哪怕调用方篡改了 _id,我们一验就能识破。

4.5 错误响应格式

写接口比写业务代码更重要的,是写好错误响应。我见过太多 "error: internal error" 这种废物信息。来,看看专业的怎么搞。

错误响应示例:

{
  "error": {
    "code": "INVALID_PLAINTEXT",
    "message": "原始数据包含不可见字符(0x00)",
    "field": "plaintext",
    "request_id": "req_55e3f7a1",
    "timestamp": "2024-09-15T10:30:01Z"
  }
}
字段 说明
code 错误码枚举,如 INVALID_PLAINTEXT、IDEMPOTENCY_CONFLICT、_TYPE_UNSUPPORTED
message 人类可读的描述,里面可以带具体异常值
field 出错的字段名,方便调用方定位
request_id 与请求 header 的 request-id 一致
timestamp 服务器生成错误的时间

4.6 核心流程总览

我把整个生成流程画成一张图,你一眼就能看明白。

令牌生成接口核心流程 客户端 API 网关 令牌服务 参数校验 幂等检查 令牌生成 返回令牌 + 元数据 校验失败 → 4xx 关键校验点 • 明文格式 & 长度 • domain 枚举值

关于幂等性的一个小故事:我曾经在生产环境碰到一个客户重试导致生成两个不同令牌的 case。后来我加了一层幂等表,用 (idempotency_key, plaintext_hash) 做联合唯一索引,这种事再没发生过。

4.7 版本化与兼容性

接口一旦发布,就得负责。我建议:

  • 路径里带版本号,比如 /api/v1/tokens,后面要改就走 /api/v2/tokens
  • 响应里不要随便删字段。如果非得删,先用 deprecated 标记警告一年
  • 请求体新增字段时要确保老客户端不传也能正常工作

举个例子:我们 v1 的 context 里只有 domain。v2 新增了 user_id,就做成可选,旧请求不传也照常生成令牌。这么做,调用方升级没压力。

4.8 小结

令牌生成接口最讨厌的就是细节。幂等键落了,日志漏了,签名没算——每一个都能让你晚上被电话叫醒。按照本章的规范来做,至少能保证你的接口在规范上不输于人。

下回你要是自己手写一个生成接口,把这些参数一张表钉在墙上,少了哪项都能一眼看出来。嗯,就这么干。