一个容易被低估的事实
很多团队对"接口安全"的理解是:加了令牌、上了 HTTPS,就算安全了。
但鉴权只解决了"你是谁",没解决"你能动什么"。
一个持有合法令牌的调用方,能不能查到别人的订单?能不能批量下载不该下载的数据?出了问题,你能不能说清楚是谁在什么时候动了哪条数据?
这三个问题,分别对应越权、暴露面和审计——它们才是电商接口里最高频的安全事故来源,而且全部发生在"一切看起来正常运行"的时候。
这一篇不讲理论,按五道防线拆开:每道防线都是工程动作,做完就能落进代码评审清单。
防线一:越权访问——最常见的洞
先分清两个词:
| 认证(Authentication) | 授权(Authorization) | |
| 回答的问题 | 你是谁 | 你能动什么 |
| 典型实现 | 令牌、签名 | 归属校验、权限范围 |
| 没做会怎样 | 陌生人进门 进了门随便翻房间 |
绝大多数事故出在第二列。
水平越权:调用方 A 拿着合法令牌,去访问调用方 B 的数据。
# 危险写法:ID 直接查,没有归属校验 GET /api/orders/{order_id} # 正确写法:查询条件里永远带上归属方 SELECT * FROM orders WHERE order_id = :order_id AND merchant_id = :current_merchant_id # 归属校验
区别只有一行:查询条件里有没有"这是谁的"。
但就是这一行,在多租户系统里决定了会不会出现 A 看到 B 的订单。而且它比想象的容易漏——列表页有归属过滤,详情页、导出接口、内部跳转链接,这三处最容易忘。
垂直越权:普通角色的令牌,调用了只有管理员才能调的接口。
解法是把权限校验做成统一网关层的一条规则,而不是散落在每个接口里靠自觉。散落的校验,迟早漏一个。
图 1 · 鉴权只是第一道门,后面四道才决定数据安不安全
防线二:暴露面——你返回的比你以为的多
第二个高频问题:接口返回的字段,比调用方需要的多。
典型场景:给前端展示订单状态的接口,顺手把手机号、完整地址、内部成本价全都返回了。
"反正前端不用就不显示"——不对。接口返回什么,就是什么。 浏览器里看到与看不到只是界面的事,数据已经到了客户端。
三个暴露面要逐个收口:
- 响应字段:按需返回。给运营看的报表接口和给买家用的查询接口,不应该是同一套字段
- 日志:请求日志、异常堆栈里经常带着手机号、地址、令牌。日志要脱敏——判断标准还是那句话:这行日志敢不敢贴到公开群里
- 错误信息:报错直接把 SQL、内部路径抛给调用方,等于把地图交出去。对外统一错误码,细节留在服务端日志里
一个实用的默认原则:白名单输出。 明确列出"这个接口允许返回哪些字段",而不是"除了敏感的都返回"。前者漏一个字段是加白名单,后者漏一个字段是事故。
防线三:输入不设防
接口的每一个参数,都是外部世界写进你系统的一次机会。
最经典的例子是拼接查询:
# 危险:字符串拼接 sql = "SELECT * FROM orders WHERE id = " + user_input # 正确:参数化查询,输入永远只是数据,不是指令 sql = "SELECT * FROM orders WHERE id = %s" cursor.execute(sql, (user_input,))
参数化查询是底线,没有例外。 在这个之上,再补两层:
- 类型与范围校验:分页大小是不是数字、有没有上限?时间区间是不是合法格式?——不合法直接拒绝,不要"尽量兼容"
- 白名单枚举:状态、排序字段这类参数,取值必须在你定义的列表里。自由文本排序字段(比如允许调用方指定任意列名)是常见漏洞点
判断标准一句话:凡是"来自外部"的值,进系统前都要过一道检查;这道检查的位置,离入口越近越好。
防线四:没有审计,出了事说不清
前面三道防线都在"防止出事",第四道回答的是另一个问题:万一出了事,你能不能还原现场?
审计日志和业务日志不是一回事:
| 业务日志 | 审计日志 | |
| 回答 | 系统发生了什么 | 谁在什么时候动了什么数据 |
| 关注 | 调试、排查 | 追责、还原、合规举证 |
| 要求 | 尽量详细,可滚动清理 | 不可篡改,留存有期限 |
审计日志要覆盖的动作,最小集合是这四类:
- 数据的读:谁批量导出过数据、导了多少
- 数据的写:谁改过订单状态、改前改后的值
- 权限的变:谁给谁加了什么权限
- 配置的变:计费规则、回调地址这类关键配置的每一次修改
批量导出单独强调:一个合法账号每天导出十万条订单,业务上没有任何报警——因为"他有权限"。但审计日志会把这个行为变成一条可见的曲线,有曲线才有异常可谈。
图 2 · 响应字段、日志、错误信息——三个都在往外送数据,各自要有收口规则
防线五:合规不是法务的事,是数据结构的事
最后一道防线,很多技术团队觉得"这是法务的事"。不是。 合规要求最终都会落成工程约束,而且越早落成本越低。
落到接口层,核心是三件事:
1. 数据最小化
只在有明确业务理由时才收集和留存数据。用不到的身份证号、完整生日,就不要从接口里拿——拿回来的每一条数据都是你的保管责任。
对应到工程上:接口申请的字段范围要能对上业务功能(这一点在鉴权篇里讲过权限最小化,是同一个原则在数据维度的延伸)。
2. 授权要留痕
用户同意了什么、什么时候同意的,要有记录。对应到接口:授权流程要走通用的授权链路,不要用"用户提供了账号密码"来代替授权——这在技术上可行,在合规上是硬伤。
3. 留存与删除
数据存多久、到期怎么删、用户要求删除时怎么删——这三个问题的答案必须是系统行为,而不是"到时候人工处理"。
# 删除不是 DELETE 一行了事,要想清楚三条线 1. 业务库:软删还是硬删,关联数据怎么级联 2. 备份与日志:留存周期到期后自动清理,而不是越积越多 3. 下游系统:你推送出去的数据,删除指令要不要同步传递
第三条最容易被忘:数据一旦通过接口流出,删除责任就跟着流到了下游。你这边删干净了,下游还留着,责任边界就说不清了。
图 3 · 收集、使用、留存、删除——每个环节都要有对应的工程机制
五道防线的自检表
- [ ] 所有按 ID 取数据的接口,查询条件里带归属校验了吗?(重点查详情页、导出、内部跳转)
- [ ] 权限校验是网关统一的,还是散在每个接口里?
- [ ] 接口输出是白名单制的吗?
- [ ] 日志和错误信息脱敏了吗?敢不敢贴到公开群?
- [ ] 所有 SQL 都是参数化的?排序、筛选这类自由参数有没有白名单?
- [ ] 四类关键动作(批量读、写、权限变、配置变)都有审计记录吗?
- [ ] 批量导出有没有可见的曲线和阈值告警?
- [ ] 接口申请的数据字段,能对上具体业务功能吗?
- [ ] 用户要求删除数据时,删除指令会不会同步到下游?
如果只能先做一条,做第一条——越权是电商接口里发生频率最高、单次影响面最大的一类问题,而且修复成本极低:一行归属校验。
小结
回到开头:加了令牌、上了 HTTPS,只是把大门关上了。
屋里还有五道防线:
- 越权 —— 每条查询都带着"这是谁的"
- 暴露面 —— 白名单输出,日志脱敏
- 输入 —— 参数化 + 白名单枚举
- 审计 —— 谁在什么时候动了什么,可还原
- 合规 —— 最小化、授权留痕、留存与删除
最后给一个判断标准:
如果今天有人拿着一个合法令牌,把你系统里的数据一条条翻出来,你多久能发现、能不能说清他翻了什么?
答不上来,说明防线四还没建——安全不是没有事故,而是出事时你看得见。