四、接口设计原则:RESTful API 设计规范、安全性优先原则
说实话,很多团队做密钥审计接口的时候,容易忽略两个核心问题:一是接口长得“太随意”,二是安全措施“事后补”。
我在刚开始带项目时,也犯过类似的错——接口命名五花八门,一个接口管三个功能,最后联调时前端崩溃、审计组查日志查到崩溃。从那以后,我给自己定了一条死线:接口设计阶段,必须同时拉齐 RESTful 规范和安全性基线。
这一章我们就聚焦这两个原则。你想想看,密钥审计接口是敏感数据的“大门”,门怎么开、谁可以进、进出有没有记录,全得在设计阶段定死。
4.1 RESTful API 设计规范
RESTful 不是什么玄学,它是一套约定俗成的资源定位和操作习惯。说白了,就是把密钥记录看成“资源”,用 HTTP 方法来表达操作。
我个人习惯在项目启动时就出一份“接口清单模板”,让前后端和审计组一起过审。下面这张表,是我常用的对照表——核心是:路径描述的是“资源名”,方法是“干什么事”。
| 动作 | HTTP 方法 | URL 示例 | 说明 |
|---|---|---|---|
| 查询审计日志列表 | GET | /api/audit/logs?page=1&size=20 | 分页读取,无副作用 |
| 查询单条密钥审计记录 | GET | /api/audit/logs/{logId} | 幂等,只读 |
| 创建一条审计记录 | POST | /api/audit/logs | 非幂等(每调用一次产生新记录) |
| 更新审计记录状态 | PUT | /api/audit/logs/{logId} | 全量替换,幂等 |
| 部分更新审计备注 | PATCH | /api/audit/logs/{logId} | 仅修改备注字段 |
| 删除审计记录 | DELETE | /api/audit/logs/{logId} | 逻辑删除或物理删除 |
/{version}/最好放在路径开头,比如 /v1/audit/logs。我见过后端改接口不升版本,导致前端全报 404 的惨案。
4.1.1 资源命名原则
资源名必须名词复数。你可以看看上面的例子,都是 logs 而不是 log。原因很简单:资源通常是一个集合,即使查一条记录,也是从集合里筛出来的。
另外,路径层次不要超过三级。比如 /v1/workspaces/{workspaceId}/keys/{keyId}/audit/logs 已经够深了。再深下去,客户端拼接路径都会崩溃。
曾经有一个项目,路径直接写成 /v1/getAuditLogByKeyId——这其实是 RPC 风格,不是 RESTful。联调时前端问了十几次“这个参数是不是必填的”。后来我强制重构成资源路径,沟通成本直接降了一半。
4.1.2 查询参数与分页
审计日志通常量很大,必须带分页。我建议统一使用 page(第几页,从 1 开始)和 size(每页条数,默认 20,最大 100)。
返回体里带上总条数 total,前端就能算总页数。举个例子:
// 请求
GET /v1/audit/logs?page=1&size=10&keyId=key-abc-123
// 响应(JSON)
{
"code": 200,
"data": {
"total": 2048,
"page": 1,
"size": 10,
"items": [
{
"logId": "log-001",
"keyId": "key-abc-123",
"action": "CREATE",
"operator": "admin",
"occurredAt": "2025-03-21T10:00:00Z"
}
]
}
}
offset 和 limit。我见过因此搞混了数据库偏移和页数的故事,最后查出来是前端传错了,后端也接错了。
4.2 安全性优先原则
密钥审计接口里存的是“谁在什么时候对哪个密钥做了什么操作”。一旦泄露,整个密钥管理体系就炸了。
安全性优先,不是“以后再加”,而是设计时就画好红线。下面这张 SVG 图,是我每次画接口设计文档时必附的“安全守卫”示意图——让你一眼看清安全控制的位置。
这张图里,我把安全措施拆成了五个部分。下面逐个说。
4.2.1 HTTPS 全链路加密
这个没得商量。密钥审计接口必须全走 HTTPS,连健康检查都不能裸奔。我在一个外部的 SDK 项目里就吃过亏——健康检查接口未加密,结果被中间人嗅探到接口路径格式,顺利伪造请求。
4.2.2 请求签名与防重放
光有 HTTPS 还不够。如果内部有服务间调用,或者客户端与后端之间有 SDK 直连,就需要签名机制。最常用的做法是:
- 客户端用
AppId + SecretKey对所有请求参数加nonce(随机数)和timestamp生成签名。 - 服务端验签,同时检查
timestamp是否在 ±5 分钟以内,以及nonce是否被用过。
这个套路能防止重放攻击。
4.2.3 最小权限与数据脱敏
不要一股脑把原始审计日志全部暴露出去。接口返回时要根据调用方的角色做字段脱敏。
比如普通运维只能看到“操作人”、“操作时间”、“操作类型”,看不到密钥内容哈希和 IP 地址。只有审计管理员才能看到完整记录。
我会在 API 网关或应用层做一层数据过滤,类似这样:
// 脱敏后的响应示例(普通用户视图)
{
"logId": "log-001",
"keyId": "key-***-123", // 部分掩码
"action": "CREATE",
"operator": "admin",
"occurredAt": "2025-03-21T10:00:00Z"
// 无 "operatorIp", 无 "keyMaterialHash"
}
4.2.4 敏感字段加密存储
审计日志里如果涉及密钥的元信息(比如密钥 ID、关联资源名称),这些字段落库时应该加密。我习惯的做法是:
- 在应用层用 AES-256 对敏感字段加密,加密密钥由 KMS 管理。
- 数据库里存密文,查询时只按时间范围,不直接在 SQL 里 WHERE 敏感字段。
- 如果必须按密钥 ID 查询,先加密用户传入的 ID,再 match 密文。
注意:加密字段要留意索引问题,建议用“哈希索引”或“加密后的固定长度字段加索引”。
4.2.5 操作日志不可篡改
审计日志本身也要被审计。你想想看,如果有人侵入数据库直接改了审计表里的记录,那整个审计体系就形同虚设。
我推荐三步走:
- 写前校验:每条记录插入时带一个哈希链字段,关联上一条日志的哈希值。
- 追加写:日志表不允许 UPDATE 和 DELETE,只允许 INSERT。
- 定期对账:每天跑一个脚本,重算哈希链,检测是否有断链或篡改。
4.3 原则落地:一个接口案例
把上面两节合并到一块,我们来看一个真实的创建审计记录接口设计。
| 条目 | 设计决策 |
|---|---|
| 资源路径 | POST /v1/audit/logs |
| 请求体 | {"keyId":"k-xxx","action":"ROTATE","operator":"system","metadata":{}} |
| 签名 | 请求头 X-Signature 包含 nonce+timestamp+bodyHash 签名 |
| 鉴权 | 仅允许 audit-writer 角色的服务账号调用 |
| 脱敏 | 返回体里 keyId 部分掩码,operator 不做掩码 |
| 落库加密 | 字段 keyId 用 AES 加密,另外存一个 sha256 索引列 |
| 防篡改 | 先查上一条日志哈希,构造新哈希,插入 prev_hash 字段 |
你看,一个简单的 POST 接口,背后绑了四条安全红线。这其实就是安全性优先原则的落地——不是等出事了再补,是在写第一行代码时就焊死在架构里。